ruff PLR6201
labelled unknown
Analysis Nemotron 3 Super
Flags membership tests using list or tuple literals where a set literal would be more efficient.
Fix: Replaces the list or tuple literal on the right-hand side of a membership test with an equivalent set literal containing the same elements.
12 hazards
- Rewriting [1, 2, [3]] to {1, 2, [3]} raises TypeError due to unhashable list element.
- Rewriting [(1,2), (3,4)] to {(1,2), (3,4)} is safe only if tuples are hashable, but fails if tuples contain unhashable items like lists.
- Rewriting [] to set() changes the type from list to set, which may break code relying on list-specific methods or ordering.
- Rewriting () to set() loses the ability to distinguish empty tuple from empty set in isinstance checks or pattern matching.
- If the left-hand side is unhashable (e.g., a list), rewriting [1,2] to {1,2} causes TypeError during membership test even if original would have returned False.
- The fix may reorder elements due to set's unordered nature, breaking code that depends on iteration order of the literal in subsequent expressions (though rare in pure membership).
- Comments attached to the list/tuple literal (e.g., x in [1, # comment 2]) may be lost or misplaced during rewrite.
- In rare cases, if the literal contains duplicate elements, the set literal removes them, changing the logical meaning if duplicates were intended (though membership test behavior is identical for duplicates).
- If the literal is used elsewhere in the same expression (e.g., x in [1,2] and [1,2].append(3)), the rewrite breaks aliasing or mutation expectations.
- Rewriting a tuple literal to a set literal may interfere with unpacking or star expressions in larger contexts if the literal is reused (though rule only triggers on simple membership).
- The fix may interfere with debugging or tracing tools that rely on the identity or type of the literal object.
- If the literal appears in a string or comment nearby, the rewrite could create confusion or false positives in manual code review.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | Tuple Literal Rewrite Hazard - Unhashable Elements | Rewriting [(1,2), (3,4)] to {(1,2), (3,4)} is unsafe when tuples contain unhashable items like lists. |
| r1-p002 | fired, no fix | Bytes vs Str Encoding Hazard | bytes vs str, encoding and newline handling, text vs binary modes |
| r1-p003 | fired, no fix | Duplicate Elements Semantic Hazard | If the literal contains duplicate elements, the set literal removes them, changing the logical meaning if duplicates were intended. |
| r1-p004 | silent prediction off | Unhashable LHS TypeError Hazard | If the left-hand side is unhashable (e.g., a list), rewriting [1,2] to {1,2} causes TypeError during membership test even if original would have returned False. |
| r1-p005 | silent | Star-Imports and Module Side Effects Hazard | star-imports, module-level side effects, __all__ |
| r1-p006 | fired, no fix prediction off | Type Annotations Runtime Evaluation Hazard | type annotations evaluated at runtime, typing-only imports |
| r1-p007 | fired, no fix prediction off | Non-File Context Manager Hazard | context managers that are not files, objects with write() but no close() |
| r1-p008 | silent prediction off | Keyword-Only and Defaults Evaluation Hazard | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r2-p001 | silent prediction off | Unhashable LHS TypeError Hazard | Rewriting [1, 2] to {1, 2} when LHS is a list causes TypeError because lists are unhashable and cannot be members of a set. |
| r2-p002 | fired, no fix | Tuple with Unhashable Element Hazard | Rewriting [(1, [2]), (3, 4)] to {(1, [2]), (3, 4)} fails because tuple (1, [2]) contains a list and is unhashable. |
| r2-p003 | fired, no fix | Comment Loss in Literal Rewrite Hazard | Comments inside the list/tuple literal may be lost or misplaced when rewriting to a set literal. |
| r2-p004 | silent | String Formatting Edge Cases Hazard | The rule should not fire within string literals (%, .format(), f-strings) as they are not actual membership tests. |
| r2-p005 | silent prediction off | Mutable Default Arguments and Aliasing Hazard | If the list/tuple is used as a mutable default argument, rewriting it to a set may break expectations if the caller mutates it (though set is immutable, the identity change may matter in aliasing chec |
| r2-p006 | silent prediction off | Generators vs Lists: Lazy Evaluation Hazard | Rewriting a generator expression to a set literal changes evaluation from lazy to eager, potentially consuming resources or causing side effects early. |
| r2-p007 | silent prediction off | Unhashable Element in List Hazard | Rewriting [1, 2, [3]] to {1, 2, [3]} raises TypeError because the list [3] is unhashable and cannot be an element of a set. |
| r2-p008 | fired, no fix | Bytes vs Str Encoding Hazard | Mixing bytes and strings in membership tests: rewriting [b'a', b'b'] to {b'a', b'b'} is safe for bytes, but comparing str to bytes set always returns False (no error), while original list comparison a |
Sources
r1-p001 · Tuple Literal Rewrite Hazard - Unhashable Elements
1data = [[1], [2]]
2items = data
3for t in [(tuple(items[0]),), (tuple(items[1]),)]:
4 try:
5 result = t in [(tuple(items[0]),), (tuple(items[1]),)]
6 print(f'Before: {t} in list -> {result}')
7 except Exception as e:
8 print(f'Before error: {type(e).__name__}: {e}')
9 try:
10 result = t in {(tuple(items[0]),), (tuple(items[1]),)}
11 print(f'After: {t} in set -> {result}')
12 except Exception as e:
13 print(f'After error: {type(e).__name__}: {e}')
Before: ((1,),) in list -> True After: ((1,),) in set -> True Before: ((2,),) in list -> True After: ((2,),) in set -> True
r1-p002 · Bytes vs Str Encoding Hazard
1bdata = b'hello\nworld'
2sdata = bdata.decode()
3for chunk in [b'hello\n', b'world']:
4 before = chunk in [b'hello\n', b'world']
5 after = chunk in {b'hello\n', b'world'}
6 print(f'Before: {chunk!r} in list -> {before}')
7 print(f'After: {chunk!r} in set -> {after}')
8 if before != after:
9 print(' DIFFERENCE DETECTED')
Before: b'hello\n' in list -> True After: b'hello\n' in set -> True Before: b'world' in list -> True After: b'world' in set -> True
r1-p003 · Duplicate Elements Semantic Hazard
1values = [1, 1, 2]
2count_before = 0
3count_after = 0
4for v in values:
5 if v in [1, 1, 2]:
6 count_before += 1
7 if v in {1, 1, 2}:
8 count_after += 1
9print(f'Before (list): {count_before} matches')
10print(f'After (set): {count_after} matches')
11if count_before != count_after:
12 print(' COUNT CHANGED: duplicates collapsed in set')
Before (list): 3 matches After (set): 3 matches
r1-p004 · Unhashable LHS TypeError Hazard
1bad_key = [1, 2]
2try:
3 res_before = bad_key in [1, 2, 3]
4 print(f'Before: {bad_key!r} in [1,2,3] -> {res_before}')
5except Exception as e:
6 print(f'Before error: {type(e).__name__}: {e}')
7try:
8 res_after = bad_key in {1, 2, 3}
9 print(f'After: {bad_key!r} in {{1,2,3}} -> {res_after}')
10except Exception as e:
11 print(f'After error: {type(e).__name__}: {e}')
Before: [1, 2] in [1,2,3] -> False After error: TypeError: cannot use 'list' as a set element (unhashable type: 'list')
r1-p005 · Star-Imports and Module Side Effects Hazard
1import sys
2mod = sys
3allowed = ['stdout', 'stderr']
4if hasattr(mod, 'stdout') and 'stdout' in allowed:
5 print('stdout access allowed')
6if hasattr(mod, 'stdout') and 'stdout' in {'stdout', 'stderr'}:
7 print('stdout access allowed (set)')
stdout access allowed stdout access allowed (set)
r1-p006 · Type Annotations Runtime Evaluation Hazard
1from typing import List
2x: List[int] = [1, 2, 3]
3if 2 in [1, 2, 3]:
4 print('Found 2 in list')
5if 2 in {1, 2, 3}:
6 print('Found 2 in set')
Found 2 in list Found 2 in set
r1-p007 · Non-File Context Manager Hazard
1class Buffer:
2 def __init__(self):
3 self.data = ''
4 def write(self, s):
5 self.data += s
6 def __enter__(self):
7 return self
8 def __exit__(self, *args):
9 pass
10buf = Buffer()
11with buf as b:
12 b.write('test')
13 if 'test' in ['test', 'foo']:
14 print('Found in list')
15 if 'test' in {'test', 'foo'}:
16 print('Found in set')
Found in list Found in set
r1-p008 · Keyword-Only and Defaults Evaluation Hazard
1def make_list():
2 return [1, 2, 3]
3def check(x, allowed=make_list()):
4 before = x in allowed
5 allowed_set = set(allowed)
6 after = x in allowed_set
7 print(f'x={x}: before={before}, after={after}')
8 return before, after
9check(2)
10check(4)
x=2: before=True, after=True x=4: before=False, after=False
r2-p001 · Unhashable LHS TypeError Hazard
1lst = [1]
2print(lst in [1, 2]) # Rule fires here
False
r2-p002 · Tuple with Unhashable Element Hazard
1tup = (1, [2])
2print(tup in [(1, [2]), (3, 4)]) # Rule fires here
True
r2-p003 · Comment Loss in Literal Rewrite Hazard
1x = 1
2print(x in [1, # comment
3 2]) # Rule fires here
True
r2-p004 · String Formatting Edge Cases Hazard
1s = 'hello'
2print(f'{s} in ["hello", "world"]') # Not a real membership test
3print('%s in ["hello", "world"]' % s) # Same
4print('{s} in ["hello", "world"]'.format(s=s)) # Same
hello in ["hello", "world"] hello in ["hello", "world"] hello in ["hello", "world"]
r2-p005 · Mutable Default Arguments and Aliasing Hazard
1def f(x, lst=[1, 2]):
2 return x in lst
3print(f(1)) # True
4print(f(3)) # False
5lst = [1, 2]
6print(f(1, lst)) # True
7lst.append(3)
8print(f(3, lst)) # True - original list mutated
True False True True
r2-p006 · Generators vs Lists: Lazy Evaluation Hazard
1def gen():
2 print('Generator called')
3 yield 1
4 yield 2
5print(1 in (x for x in gen())) # Rule does NOT fire: generator expression
6# But if we had: 1 in [x for x in gen()] -> rule fires, eager eval
7lst = [x for x in gen()] # Rule fires here: list comprehension
8print(1 in lst) # Already printed 'Generator called' twice
Generator called True Generator called True
r2-p007 · Unhashable Element in List Hazard
1lst = [3]
2print(lst in [1, 2, [3]]) # Rule fires here: evaluates to True
True
r2-p008 · Bytes vs Str Encoding Hazard
1b = b'hello'
2print(b in [b'hello', b'world']) # Rule fires here: True
3s = 'hello'
4print(s in [b'hello', b'world']) # Rule fires here: False (str not in bytes list)
True False