fixhunt

ruff SIM911: fix changes program behavior

fix-changes-behaviorlabelled safereal · highrelated
Findings / 2026-10-17T20-38-44-795Z-ekj4-ruff-SIM911 / r1-p006ruff 0.17.113 → 5 lines2026-10-17 20:38 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

string formatting edge cases: %, format, f-strings, repr vs str, nested quotes

Program

before
1d = {'a': 1, 'b': 2}
2zipped = zip(d.keys(), d.values())
3for k, v in zipped:
4 s = f'{k!r}:{v!r}'
5print(repr(list(zipped)))
after
1d = {'a': 1, 'b': 2}
2zipped = d.items()
3for k, v in zipped:
4 s = f'{k!r}:{v!r}'
5print(repr(list(zipped)))

Output

stdout beforeexit 0
[]
stdout afterexit 0
[('a', 1), ('b', 2)]
fired
1 before · 0 after
runs
each version twice, same file name, no network

Judge Nemotron 3 Ultra advisory real · high

The rule rewrites `zip(d.keys(), d.values())` to `d.items()`, but `zip` returns a reusable iterator while `dict.items()` returns a view that reflects the dictionary's current state. In the program, the view is consumed by the for-loop, so `list(zipped)` afterwards yields the items, whereas the original exhausted zip iterator yields an empty list. This is a silent change in runtime behavior for plausible code.

Explanation Nemotron 3 Super unverified

The original `zip` creates an iterator consumed by the loop; afterwards it’s exhausted, so converting it to a list yields []. Replacing it with `dict.items()` returns a view that remains reusable—iterating over it does not consume the view itself—so the subsequent `list(zipped)` captures all items, changing the output from [] to [('a', 1), ('b', 2)]. This alters program behavior, making the rewrite unsafe.

Reproduce

file
prog.py (the before program)
minimized program
r1-p006.py
evidence sheet
r1-p006.md
regression fixture
r1-p006.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 SIM911 --fix prog.py
python3 prog.py

Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra

SIM911 fix · SIM911 dict keys values zip · Ruff lint zip dictionary iteration · Rule SIM911 replace zip with items

Details

program
String Formatting Edge Cases
hazard
string formatting edge cases: %, format, f-strings, repr vs str, nested quotes
oracle
stdout before: [] stdout after: [('a', 1), ('b', 2)]
run
2026-10-17T20-38-44-795Z-ekj4-ruff-SIM911
generated program (13 lines) and the fix's diff
1d = {'a': 1, 'b': 2}
2zipped = zip(d.keys(), d.values())
3for k, v in zipped:
4 s = f'{k!r}:{v!r}'
5 print(s)
6# After fix, same output expected
7items = d.items()
8for k, v in items:
9 s = '{!r}:{!r}'.format(k, v)
10 print(s)
11# Test repr vs str
12print(repr(list(zipped)))
13print(str(list(items)))
--- orig.py	2026-10-17 16:38:58.621792777 -0400
+++ fixed.py	2026-10-17 16:38:58.785800554 -0400
@@ -1,5 +1,5 @@
 d = {'a': 1, 'b': 2}
-zipped = zip(d.keys(), d.values())
+zipped = d.items()
 for k, v in zipped:
     s = f'{k!r}:{v!r}'
     print(s)
rule documentation given to the models
# zip-dict-keys-and-values (SIM911)

Derived from the **flake8-simplify** linter.

Fix is always available.

## What it does
Checks for use of `zip()` to iterate over keys and values of a dictionary at once.

## Why is this bad?
The `dict` type provides an `.items()` method which is faster and more readable.

## Example
```python
flag_stars = {"USA": 50, "Slovenia": 3, "Panama": 2, "Australia": 6}

for country, stars in zip(flag_stars.keys(), flag_stars.values()):
    print(f"{country}'s flag has {stars} stars.")
```

Use instead:
```python
flag_stars = {"USA": 50, "Slovenia": 3, "Panama": 2, "Australia": 6}

for country, stars in flag_stars.items():
    print(f"{country}'s flag has {stars} stars.")
```

## Fix safety
This rule's fix is marked as safe, unless the expression contains comments.

## References
- [Python documentation: `dict.items`](https://docs.python.org/3/library/stdtypes.html#dict.items)