@@ -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