@@ -1921,14 +1921,30 @@ def _check_unnecessary_dict_index_lookup(
19211921 return
19221922 iterating_object_name = node .iter .func .expr .as_string ()
19231923
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+
19241928 # Verify that the body of the for loop uses a subscript
19251929 # with the object that was iterated. This uses some heuristics
19261930 # in order to make sure that the same object is used in the
19271931 # for body.
19281932
19291933 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
19311945 )
1946+ has_nested_loops = next (nested_loops , None ) is not None
1947+
19321948 for child in children :
19331949 for subscript in child .nodes_of_class (nodes .Subscript ):
19341950 if not isinstance (subscript .value , (nodes .Name , nodes .Attribute )):
@@ -1968,11 +1984,19 @@ def _check_unnecessary_dict_index_lookup(
19681984 # defined and compare that to the for loop's line number
19691985 continue
19701986
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+ )
19762000
19772001 # Case where .items is assigned to single var (i.e., for item in d.items())
19782002 elif isinstance (value , nodes .Subscript ):
@@ -1999,11 +2023,31 @@ def _check_unnecessary_dict_index_lookup(
19992023 inferred = utils .safe_infer (value .slice )
20002024 if not isinstance (inferred , nodes .Const ) or inferred .value != 0 :
20012025 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+ )
20072051
20082052 def _check_unnecessary_list_index_lookup (
20092053 self , node : nodes .For | nodes .Comprehension
@@ -2029,9 +2073,25 @@ def _check_unnecessary_list_index_lookup(
20292073 iterating_object_name = node .iter .args [0 ].name
20302074 value_variable = node .target .elts [1 ]
20312075
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+
20322080 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
20342092 )
2093+ has_nested_loops = next (nested_loops , None ) is not None
2094+
20352095 for child in children :
20362096 for subscript in child .nodes_of_class (nodes .Subscript ):
20372097 if isinstance (node , nodes .For ) and _is_part_of_assignment_target (
@@ -2070,9 +2130,23 @@ def _check_unnecessary_list_index_lookup(
20702130 # reassigned on a later line, so it can't be used.
20712131 continue
20722132
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