@@ -1921,14 +1921,30 @@ def _check_unnecessary_dict_index_lookup(
1921
1921
return
1922
1922
iterating_object_name = node .iter .func .expr .as_string ()
1923
1923
1924
+ # Store potential violations. These will only be reported if we don't
1925
+ # discover any writes to the collection during the loop.
1926
+ messages = []
1927
+
1924
1928
# Verify that the body of the for loop uses a subscript
1925
1929
# with the object that was iterated. This uses some heuristics
1926
1930
# in order to make sure that the same object is used in the
1927
1931
# for body.
1928
1932
1929
1933
children = (
1930
- node .body if isinstance (node , nodes .For ) else node .parent .get_children ()
1934
+ node .body
1935
+ if isinstance (node , nodes .For )
1936
+ else list (node .parent .get_children ())
1937
+ )
1938
+
1939
+ # Check if there are any for / while loops within the loop in question;
1940
+ # If so, we will be more conservative about reporting errors as we
1941
+ # can't yet do proper control flow analysis to be sure when
1942
+ # reassignment will affect us
1943
+ nested_loops = itertools .chain .from_iterable (
1944
+ child .nodes_of_class ((nodes .For , nodes .While )) for child in children
1931
1945
)
1946
+ has_nested_loops = next (nested_loops , None ) is not None
1947
+
1932
1948
for child in children :
1933
1949
for subscript in child .nodes_of_class (nodes .Subscript ):
1934
1950
if not isinstance (subscript .value , (nodes .Name , nodes .Attribute )):
@@ -1968,11 +1984,19 @@ def _check_unnecessary_dict_index_lookup(
1968
1984
# defined and compare that to the for loop's line number
1969
1985
continue
1970
1986
1971
- self .add_message (
1972
- "unnecessary-dict-index-lookup" ,
1973
- node = subscript ,
1974
- args = (node .target .elts [1 ].as_string ()),
1975
- )
1987
+ if has_nested_loops :
1988
+ messages .append (
1989
+ {
1990
+ "node" : subscript ,
1991
+ "variable" : node .target .elts [1 ].as_string (),
1992
+ }
1993
+ )
1994
+ else :
1995
+ self .add_message (
1996
+ "unnecessary-dict-index-lookup" ,
1997
+ node = subscript ,
1998
+ args = (node .target .elts [1 ].as_string (),),
1999
+ )
1976
2000
1977
2001
# Case where .items is assigned to single var (i.e., for item in d.items())
1978
2002
elif isinstance (value , nodes .Subscript ):
@@ -1999,11 +2023,31 @@ def _check_unnecessary_dict_index_lookup(
1999
2023
inferred = utils .safe_infer (value .slice )
2000
2024
if not isinstance (inferred , nodes .Const ) or inferred .value != 0 :
2001
2025
continue
2002
- self .add_message (
2003
- "unnecessary-dict-index-lookup" ,
2004
- node = subscript ,
2005
- args = ("1" .join (value .as_string ().rsplit ("0" , maxsplit = 1 )),),
2006
- )
2026
+
2027
+ if has_nested_loops :
2028
+ messages .append (
2029
+ {
2030
+ "node" : subscript ,
2031
+ "variable" : "1" .join (
2032
+ value .as_string ().rsplit ("0" , maxsplit = 1 )
2033
+ ),
2034
+ }
2035
+ )
2036
+ else :
2037
+ self .add_message (
2038
+ "unnecessary-dict-index-lookup" ,
2039
+ node = subscript ,
2040
+ args = (
2041
+ "1" .join (value .as_string ().rsplit ("0" , maxsplit = 1 )),
2042
+ ),
2043
+ )
2044
+
2045
+ for message in messages :
2046
+ self .add_message (
2047
+ "unnecessary-dict-index-lookup" ,
2048
+ node = message ["node" ],
2049
+ args = (message ["variable" ],),
2050
+ )
2007
2051
2008
2052
def _check_unnecessary_list_index_lookup (
2009
2053
self , node : nodes .For | nodes .Comprehension
@@ -2029,9 +2073,25 @@ def _check_unnecessary_list_index_lookup(
2029
2073
iterating_object_name = node .iter .args [0 ].name
2030
2074
value_variable = node .target .elts [1 ]
2031
2075
2076
+ # Store potential violations. These will only be reported if we don't
2077
+ # discover any writes to the collection during the loop.
2078
+ bad_nodes = []
2079
+
2032
2080
children = (
2033
- node .body if isinstance (node , nodes .For ) else node .parent .get_children ()
2081
+ node .body
2082
+ if isinstance (node , nodes .For )
2083
+ else list (node .parent .get_children ())
2084
+ )
2085
+
2086
+ # Check if there are any for / while loops within the loop in question;
2087
+ # If so, we will be more conservative about reporting errors as we
2088
+ # can't yet do proper control flow analysis to be sure when
2089
+ # reassignment will affect us
2090
+ nested_loops = itertools .chain .from_iterable (
2091
+ child .nodes_of_class ((nodes .For , nodes .While )) for child in children
2034
2092
)
2093
+ has_nested_loops = next (nested_loops , None ) is not None
2094
+
2035
2095
for child in children :
2036
2096
for subscript in child .nodes_of_class (nodes .Subscript ):
2037
2097
if isinstance (node , nodes .For ) and _is_part_of_assignment_target (
@@ -2070,9 +2130,23 @@ def _check_unnecessary_list_index_lookup(
2070
2130
# reassigned on a later line, so it can't be used.
2071
2131
continue
2072
2132
2073
- self .add_message (
2074
- "unnecessary-list-index-lookup" ,
2075
- node = subscript ,
2076
- args = (node .target .elts [1 ].name ,),
2077
- confidence = HIGH ,
2078
- )
2133
+ if has_nested_loops :
2134
+ # Have found a likely issue, but since there are nested
2135
+ # loops we don't want to report this unless we get to the
2136
+ # end of the loop without updating the collection
2137
+ bad_nodes .append (subscript )
2138
+ else :
2139
+ self .add_message (
2140
+ "unnecessary-list-index-lookup" ,
2141
+ node = subscript ,
2142
+ args = (node .target .elts [1 ].name ,),
2143
+ confidence = HIGH ,
2144
+ )
2145
+
2146
+ for subscript in bad_nodes :
2147
+ self .add_message (
2148
+ "unnecessary-list-index-lookup" ,
2149
+ node = subscript ,
2150
+ args = (node .target .elts [1 ].name ,),
2151
+ confidence = HIGH ,
2152
+ )
0 commit comments