Skip to content

Commit a663852

Browse files
committed
fix self.in_comp_inner_loop_target
1 parent bbbad66 commit a663852

File tree

2 files changed

+96
-68
lines changed

2 files changed

+96
-68
lines changed

Lib/test/test_named_expressions.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,6 @@ def test_named_expression_invalid_rebinding_list_comprehension_iteration_variabl
165165
with self.assertRaisesRegex(SyntaxError, msg):
166166
exec(code, {}, {})
167167

168-
# TODO: RUSTPYTHON
169-
@unittest.expectedFailure # wrong error message
170168
def test_named_expression_invalid_rebinding_list_comprehension_inner_loop(self):
171169
cases = [
172170
("Inner reuse", 'j', "[i for i in range(5) if (j := 0) for j in range(5)]"),
@@ -223,8 +221,6 @@ def test_named_expression_invalid_rebinding_set_comprehension_iteration_variable
223221
with self.assertRaisesRegex(SyntaxError, msg):
224222
exec(code, {}, {})
225223

226-
# TODO: RUSTPYTHON
227-
@unittest.expectedFailure
228224
def test_named_expression_invalid_rebinding_set_comprehension_inner_loop(self):
229225
cases = [
230226
("Inner reuse", 'j', "{i for i in range(5) if (j := 0) for j in range(5)}"),

crates/codegen/src/symboltable.rs

Lines changed: 96 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,10 @@ struct SymbolTableBuilder {
625625
in_annotation: bool,
626626
// Track if we're inside a type alias (yield/await/named expr not allowed)
627627
in_type_alias: bool,
628+
// Track if we're scanning an inner loop iteration target (not the first generator)
629+
in_comp_inner_loop_target: bool,
630+
// Scope info for error messages (e.g., "a TypeVar bound")
631+
scope_info: Option<&'static str>,
628632
}
629633

630634
/// Enum to indicate in what mode an expression
@@ -651,6 +655,8 @@ impl SymbolTableBuilder {
651655
in_iter_def_exp: false,
652656
in_annotation: false,
653657
in_type_alias: false,
658+
in_comp_inner_loop_target: false,
659+
scope_info: None,
654660
};
655661
this.enter_scope("top", CompilerScope::Module, 0);
656662
this
@@ -1084,62 +1090,40 @@ impl SymbolTableBuilder {
10841090
) -> SymbolTableResult {
10851091
use ruff_python_ast::*;
10861092

1087-
// Check for expressions not allowed in type parameters scope
1088-
if let Some(table) = self.tables.last()
1089-
&& table.typ == CompilerScope::TypeParams
1090-
&& let Some(keyword) = match expression {
1091-
Expr::Yield(_) | Expr::YieldFrom(_) => Some("yield"),
1092-
Expr::Await(_) => Some("await"),
1093-
Expr::Named(_) => Some("named"),
1094-
_ => None,
1095-
}
1096-
{
1097-
return Err(SymbolTableError {
1098-
error: format!("{keyword} expression cannot be used within a type parameter"),
1099-
location: Some(
1100-
self.source_file
1101-
.to_source_code()
1102-
.source_location(expression.range().start(), PositionEncoding::Utf8),
1103-
),
1104-
});
1105-
}
1106-
1107-
// Check for expressions not allowed in annotations
1108-
if self.in_annotation
1109-
&& let Some(keyword) = match expression {
1110-
Expr::Yield(_) | Expr::YieldFrom(_) => Some("'yield'"),
1111-
Expr::Await(_) => Some("'await'"),
1112-
Expr::Named(_) => Some("named expression"),
1113-
_ => None,
1114-
}
1115-
{
1116-
return Err(SymbolTableError {
1117-
error: format!("{keyword} cannot be used within an annotation"),
1118-
location: Some(
1119-
self.source_file
1120-
.to_source_code()
1121-
.source_location(expression.range().start(), PositionEncoding::Utf8),
1122-
),
1123-
});
1124-
}
1093+
// Check for expressions not allowed in certain contexts
1094+
// (type parameters, annotations, type aliases, TypeVar bounds/defaults)
1095+
if let Some(keyword) = match expression {
1096+
Expr::Yield(_) | Expr::YieldFrom(_) => Some("yield"),
1097+
Expr::Await(_) => Some("await"),
1098+
Expr::Named(_) => Some("named"),
1099+
_ => None,
1100+
} {
1101+
// Determine the context name for the error message
1102+
// scope_info takes precedence (e.g., "a TypeVar bound")
1103+
let context_name = if let Some(scope_info) = self.scope_info {
1104+
Some(scope_info)
1105+
} else if let Some(table) = self.tables.last()
1106+
&& table.typ == CompilerScope::TypeParams
1107+
{
1108+
Some("a type parameter")
1109+
} else if self.in_annotation {
1110+
Some("an annotation")
1111+
} else if self.in_type_alias {
1112+
Some("a type alias")
1113+
} else {
1114+
None
1115+
};
11251116

1126-
// Check for expressions not allowed in type alias
1127-
if self.in_type_alias
1128-
&& let Some(keyword) = match expression {
1129-
Expr::Yield(_) | Expr::YieldFrom(_) => Some("'yield'"),
1130-
Expr::Await(_) => Some("'await'"),
1131-
Expr::Named(_) => Some("named expression"),
1132-
_ => None,
1117+
if let Some(context_name) = context_name {
1118+
return Err(SymbolTableError {
1119+
error: format!("{keyword} expression cannot be used within {context_name}"),
1120+
location: Some(
1121+
self.source_file
1122+
.to_source_code()
1123+
.source_location(expression.range().start(), PositionEncoding::Utf8),
1124+
),
1125+
});
11331126
}
1134-
{
1135-
return Err(SymbolTableError {
1136-
error: format!("{keyword} cannot be used within a type alias"),
1137-
location: Some(
1138-
self.source_file
1139-
.to_source_code()
1140-
.source_location(expression.range().start(), PositionEncoding::Utf8),
1141-
),
1142-
});
11431127
}
11441128

11451129
match expression {
@@ -1485,7 +1469,13 @@ impl SymbolTableBuilder {
14851469

14861470
let mut is_first_generator = true;
14871471
for generator in generators {
1472+
// Set flag for INNER_LOOP_CONFLICT check (only for inner loops, not the first)
1473+
if !is_first_generator {
1474+
self.in_comp_inner_loop_target = true;
1475+
}
14881476
self.scan_expression(&generator.target, ExpressionContext::Iter)?;
1477+
self.in_comp_inner_loop_target = false;
1478+
14891479
if is_first_generator {
14901480
is_first_generator = false;
14911481
} else {
@@ -1508,19 +1498,29 @@ impl SymbolTableBuilder {
15081498

15091499
/// Scan type parameter bound or default in a separate scope
15101500
// = symtable_visit_type_param_bound_or_default
1511-
fn scan_type_param_bound_or_default(&mut self, expr: &Expr, name: &str) -> SymbolTableResult {
1501+
fn scan_type_param_bound_or_default(
1502+
&mut self,
1503+
expr: &Expr,
1504+
scope_name: &str,
1505+
scope_info: &'static str,
1506+
) -> SymbolTableResult {
15121507
// Enter a new TypeParams scope for the bound/default expression
15131508
// This allows the expression to access outer scope symbols
15141509
let line_number = self.line_index_start(expr.range());
1515-
self.enter_scope(name, CompilerScope::TypeParams, line_number);
1510+
self.enter_scope(scope_name, CompilerScope::TypeParams, line_number);
15161511

15171512
// Note: In CPython, can_see_class_scope is preserved in the new scope
15181513
// In RustPython, this is handled through the scope hierarchy
15191514

1515+
// Set scope_info for better error messages
1516+
let old_scope_info = self.scope_info;
1517+
self.scope_info = Some(scope_info);
1518+
15201519
// Scan the expression in this new scope
15211520
let result = self.scan_expression(expr, ExpressionContext::Load);
15221521

1523-
// Exit the scope
1522+
// Restore scope_info and exit the scope
1523+
self.scope_info = old_scope_info;
15241524
self.leave_scope();
15251525

15261526
result
@@ -1564,18 +1564,25 @@ impl SymbolTableBuilder {
15641564

15651565
// Process bound in a separate scope
15661566
if let Some(binding) = bound {
1567-
let scope_name = if binding.is_tuple_expr() {
1568-
format!("<TypeVar constraint of {name}>")
1567+
let (scope_name, scope_info) = if binding.is_tuple_expr() {
1568+
(
1569+
format!("<TypeVar constraint of {name}>"),
1570+
"a TypeVar constraint",
1571+
)
15691572
} else {
1570-
format!("<TypeVar bound of {name}>")
1573+
(format!("<TypeVar bound of {name}>"), "a TypeVar bound")
15711574
};
1572-
self.scan_type_param_bound_or_default(binding, &scope_name)?;
1575+
self.scan_type_param_bound_or_default(binding, &scope_name, scope_info)?;
15731576
}
15741577

15751578
// Process default in a separate scope
15761579
if let Some(default_value) = default {
15771580
let scope_name = format!("<TypeVar default of {name}>");
1578-
self.scan_type_param_bound_or_default(default_value, &scope_name)?;
1581+
self.scan_type_param_bound_or_default(
1582+
default_value,
1583+
&scope_name,
1584+
"a TypeVar default",
1585+
)?;
15791586
}
15801587
}
15811588
TypeParam::ParamSpec(TypeParamParamSpec {
@@ -1589,7 +1596,11 @@ impl SymbolTableBuilder {
15891596
// Process default in a separate scope
15901597
if let Some(default_value) = default {
15911598
let scope_name = format!("<ParamSpec default of {name}>");
1592-
self.scan_type_param_bound_or_default(default_value, &scope_name)?;
1599+
self.scan_type_param_bound_or_default(
1600+
default_value,
1601+
&scope_name,
1602+
"a ParamSpec default",
1603+
)?;
15931604
}
15941605
}
15951606
TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
@@ -1603,7 +1614,11 @@ impl SymbolTableBuilder {
16031614
// Process default in a separate scope
16041615
if let Some(default_value) = default {
16051616
let scope_name = format!("<TypeVarTuple default of {name}>");
1606-
self.scan_type_param_bound_or_default(default_value, &scope_name)?;
1617+
self.scan_type_param_bound_or_default(
1618+
default_value,
1619+
&scope_name,
1620+
"a TypeVarTuple default",
1621+
)?;
16071622
}
16081623
}
16091624
}
@@ -1742,6 +1757,23 @@ impl SymbolTableBuilder {
17421757
// Some checks for the symbol that present on this scope level:
17431758
let symbol = if let Some(symbol) = table.symbols.get_mut(name.as_ref()) {
17441759
let flags = &symbol.flags;
1760+
1761+
// INNER_LOOP_CONFLICT: comprehension inner loop cannot rebind
1762+
// a variable that was used as a named expression target
1763+
// Example: [i for i in range(5) if (j := 0) for j in range(5)]
1764+
// Here 'j' is used in named expr first, then as inner loop iter target
1765+
if self.in_comp_inner_loop_target
1766+
&& flags.contains(SymbolFlags::ASSIGNED_IN_COMPREHENSION)
1767+
{
1768+
return Err(SymbolTableError {
1769+
error: format!(
1770+
"comprehension inner loop cannot rebind assignment expression target '{}'",
1771+
name
1772+
),
1773+
location,
1774+
});
1775+
}
1776+
17451777
// Role already set..
17461778
match role {
17471779
SymbolUsage::Global if !symbol.is_global() => {

0 commit comments

Comments
 (0)