@@ -22,14 +22,14 @@ def visit_ListComp(self, ast_node: ast.ListComp) -> None:
2222 # Validate targets in generators (the 'v' in 'for v in lst')
2323 for one_comprehension in ast_node .generators :
2424 if not self ._is_partial_unpacking (ast_node .elt , one_comprehension .target ):
25- self ._validate_comprehension_target (one_comprehension .target )
25+ self ._validate_comprehension_target (one_comprehension .target , one_comprehension . iter )
2626 self .generic_visit (ast_node )
2727
2828 def visit_SetComp (self , ast_node : ast .SetComp ) -> None :
2929 # Validate targets in generators (the 'v' in 'for v in lst')
3030 for one_comprehension in ast_node .generators :
3131 if not self ._is_partial_unpacking (ast_node .elt , one_comprehension .target ):
32- self ._validate_comprehension_target (one_comprehension .target )
32+ self ._validate_comprehension_target (one_comprehension .target , one_comprehension . iter )
3333 self .generic_visit (ast_node )
3434
3535 def visit_DictComp (self , ast_node : ast .DictComp ) -> None :
@@ -38,22 +38,22 @@ def visit_DictComp(self, ast_node: ast.DictComp) -> None:
3838 # key and value are both used
3939 for one_comprehension in ast_node .generators :
4040 if not self ._is_partial_unpacking_expr_count (2 , one_comprehension .target ):
41- self ._validate_comprehension_target (one_comprehension .target )
41+ self ._validate_comprehension_target (one_comprehension .target , one_comprehension . iter )
4242 self .generic_visit (ast_node )
4343
4444 def visit_For (self , ast_node : ast .For ) -> None :
4545 # Validate target variables in regular for-loops
4646 # Apply same unpacking logic as comprehensions
4747 # For-loops don't have an expression that references vars
4848 if not self ._is_partial_unpacking_expr_count (1 , ast_node .target ):
49- self ._validate_comprehension_target (ast_node .target )
49+ self ._validate_comprehension_target (ast_node .target , ast_node . iter )
5050 self .generic_visit (ast_node )
5151
5252 def visit_GeneratorExp (self , ast_node : ast .GeneratorExp ) -> None :
5353 # Validate targets in generators (the 'v' in 'for v in lst')
5454 for one_comprehension in ast_node .generators :
5555 if not self ._is_partial_unpacking (ast_node .elt , one_comprehension .target ):
56- self ._validate_comprehension_target (one_comprehension .target )
56+ self ._validate_comprehension_target (one_comprehension .target , one_comprehension . iter )
5757 self .generic_visit (ast_node )
5858
5959 def _is_partial_unpacking (self , expression : ast .expr , target_node : ast .expr ) -> bool :
@@ -65,6 +65,22 @@ def _is_partial_unpacking_expr_count(self, expression_count: int, target_node: a
6565 target_count : typing .Final = self ._count_unpacked_vars (target_node )
6666 return target_count > expression_count and target_count > 1
6767
68+ def _is_literal_range (self , iter_node : ast .expr ) -> bool :
69+ """Check if the iteration is over a literal range() call."""
70+ # Check for direct range() call
71+ if isinstance (iter_node , ast .Call ) and isinstance (iter_node .func , ast .Name ) and iter_node .func .id == "range" :
72+ # Check if all arguments are literals (no variables)
73+ for one_arg in iter_node .args :
74+ if not isinstance (one_arg , (ast .Constant , ast .UnaryOp )):
75+ # If any argument is not a literal, this is not a literal range
76+ # Note: UnaryOp is included to handle negative numbers like -1
77+ return False
78+ # For UnaryOp (like -1), check if operand is a literal
79+ if isinstance (one_arg , ast .UnaryOp ) and not isinstance (one_arg .operand , ast .Constant ):
80+ return False
81+ return True
82+ return False
83+
6884 def _count_referenced_vars (self , expression : ast .expr ) -> int :
6985 """Count how many variables are referenced in the expression."""
7086 if isinstance (expression , ast .Name ):
@@ -83,8 +99,12 @@ def _count_unpacked_vars(self, target_node: ast.expr) -> int:
8399 return len ([one_element for one_element in target_node .elts if isinstance (one_element , ast .Name )])
84100 return 0
85101
86- def _validate_comprehension_target (self , target_node : ast .expr ) -> None :
102+ def _validate_comprehension_target (self , target_node : ast .expr , iter_node : ast . expr | None = None ) -> None :
87103 """Validate that comprehension target follows the one_ prefix rule."""
104+ # Skip validation if iterating over literal range()
105+ if iter_node is not None and self ._is_literal_range (iter_node ):
106+ return
107+
88108 # Skip ignored targets (underscore, unpacking)
89109 if _is_ignored_target (target_node ):
90110 return
0 commit comments