fixhunt

ruff PLR1736: fix changes program behavior

fix-changes-behaviorlabelled safereal · highduplicate
Findings / 2026-10-17T21-04-33-755Z-llwi-ruff-PLR1736 / r2-p004ruff 0.17.110 → 6 lines2026-10-17 21:04 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

If the loop variable is named identically to the list (e.g., `for lst, lst in enumerate(lst): lst[lst]`), the fix risks incorrect variable capture due to name shadowing.

Program

before
1lst = ['a', 'b', 'c']
2for lst, lst in enumerate(lst): # outer lst shadows inner? actually, first is index, second is element
3 try:
4 result = lst[lst] # original: indexes the list with the element (string) -> error
5 except Exception as e:
6 print(f'{type(e).__name__}: {e}', end=' ')
after
1lst = ['a', 'b', 'c']
2for lst, lst in enumerate(lst): # outer lst shadows inner? actually, first is index, second is element
3 try:
4 result = lst # original: indexes the list with the element (string) -> error
5 except Exception as e:
6 print(f'{type(e).__name__}: {e}', end=' ')

Output

stdout beforeexit 0
TypeError: string indices must be integers, not 'str' TypeError: string indices must be integers, not 'str' TypeError: string indices must be integers, not 'str'
stdout afterexit 0
(no stdout)
fired
1 before · 0 after
runs
each version twice, same file name, no network

Judge Nemotron 3 Ultra advisory real · high

The rule assumes the enumerate target is a list and the loop variables are distinct (index, item), so it replaces lst[lst] with lst. In the program the same name lst is used for both variables; the inner lst binds to the element (a string), making the original indexing a TypeError that is caught and printed. After the fix the indexing is removed, the exception disappears, and the program prints nothing — a silent change in observable behavior.

Explanation Nemotron 3 Super unverified

The rewrite replaces `result = lst[lst]` with `result = lst`, changing the operation from indexing the list using its current element (which raises a TypeError because strings cannot index lists) to simply assigning the loop variable itself. This eliminates the exception entirely, so no output is produced where three TypeErrors were previously printed. The behavior differs because the original code intentionally caused and caught errors, while the fix suppresses them by altering the logic inside the try block. Thus, the transformation is not semantics-preserving—it changes what the program does, not just how it expresses it. The label "safe" is incorrect in this context.

Reproduce

file
prog.py (the before program)
minimized program
r2-p004.py
evidence sheet
r2-p004.md
regression fixture
r2-p004.py the minimized program with a header stating the observed change, for the tool's test suite
python3 prog.py
ruff check --preview --isolated --select PLR1736 --fix prog.py
python3 prog.py

Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra

PLR1736 fix · ruff PLR1736 variable shadowing · enumerate loop duplicate variables · list indexing with non-integer

  • related astral-sh/ruff/issues/29422 umbrella issue listing PLR1736 among many rules but does not describe this specific shadowed-variable construct
  • related astral-sh/ruff/issues/25182 tracks additional false positives for PLR1736 but focuses on dict iteration, not shadowed loop variables
  • related astral-sh/ruff/issues/12594 PLR1736 defect about custom start index in enumerate, different construct from shadowed loop variables
  • related astral-sh/ruff/issues/9010 autofix conflict between PLR1736 and FURB148, different root cause than shadowed-variable rewrite
  • duplicate astral-sh/ruff/issues/8969 explicitly reports PLR1736 yields invalid suggestion and unsafe fix that changes semantics on shadowed loop variables like `for i, i in enumerate(...)`

Details

program
Variable capture due to name shadowing in enumerate
hazard
If the loop variable is named identically to the list (e.g., `for lst, lst in enumerate(lst): lst[lst]`), the fix risks incorrect variable capture due to name shadowing.
oracle
stdout before: TypeError: string indices must be integers, not 'str' TypeError: string indices must be integers, not 'str' TypeError: string indices must be integers, not 'str' stdout after:
run
2026-10-17T21-04-33-755Z-llwi-ruff-PLR1736
generated program (10 lines) and the fix's diff
1lst = ['a', 'b', 'c']
2for lst, lst in enumerate(lst): # outer lst shadows inner? actually, first is index, second is element
3 # Rule sees: lst[lst] -> wants to replace with lst (element), but lst is now element, not list
4 # So original: lst[index] -> element at index
5 # Fix: lst (element) -> which is a string, not indexable -> TypeError
6 try:
7 result = lst[lst] # original: indexes the list with the element (string) -> error
8 except Exception as e:
9 print(f'{type(e).__name__}: {e}', end=' ')
10print()
--- orig.py	2026-10-17 17:05:01.732035658 -0400
+++ fixed.py	2026-10-17 17:05:01.900037542 -0400
@@ -4,7 +4,7 @@
     # So original: lst[index] -> element at index
     # Fix: lst (element) -> which is a string, not indexable -> TypeError
     try:
-        result = lst[lst]  # original: indexes the list with the element (string) -> error
+        result = lst  # original: indexes the list with the element (string) -> error
     except Exception as e:
         print(f'{type(e).__name__}: {e}', end=' ')
 print()
rule documentation given to the models
# unnecessary-list-index-lookup (PLR1736)

Derived from the **Pylint** linter.

Fix is always available.

## What it does
Checks for index-based list accesses during `enumerate` iterations.

## Why is this bad?
When iterating over a list with `enumerate`, the current item is already
available alongside its index. Using the index to look up the item is
unnecessary.

## Example
```python
letters = ["a", "b", "c"]

for index, letter in enumerate(letters):
    print(letters[index])
```

Use instead:
```python
letters = ["a", "b", "c"]

for index, letter in enumerate(letters):
    print(letter)
```