ruff PLC0208
labelled unknown
Analysis Nemotron 3 Super
The rule flags iteration over set literals containing only literal elements.
Fix: Replace the set literal with a tuple literal containing the same elements in arbitrary order, preserving all literal values exactly as written.
10 hazards
- Rewriting a set literal used elsewhere in the same scope changes behavior if the original set was referenced by variable or attribute after the loop, as the tuple replacement is not equivalent for membership tests or mutation.
- If the set literal appears in a context where its unhashable nature is relied upon (e.g., passed to a function expecting a set), replacing it with a tuple may cause TypeError or logic errors due to type mismatch.
- When the set literal is part of a larger expression (e.g., union or intersection with another set), replacing it with a tuple breaks the operation as tuples do not support set methods.
- If the loop variable is mutated within the loop body and later used to reconstruct a set, the original iteration order (arbitrary but consistent per run) may affect downstream logic differently than tuple order.
- In rare cases where the set literal's hash randomization affects iteration order across runs, replacing it with a fixed-order tuple may make behavior deterministic where it was previously nondeterministic, altering probabilistic outcomes.
- If the set literal contains duplicate literals (e.g., {1, 1, 2}), the set deduplicates them, but the tuple rewrite preserves duplicates, changing the number of iterations and potentially breaking logic relying on unique iteration.
- Comments attached to the set literal (e.g., {1, 2 # inline}) may be lost or misplaced during the rewrite, as the autofix typically does not preserve comment placement within the literal.
- If the set literal is defined on a line with a trailing comma (e.g., {1, 2,}), the autofix may incorrectly omit or mishandle the comma when generating the tuple, leading to a syntax error or unintended single-element tuple.
- When the set literal is used in a default argument or class attribute, replacing it with a tuple may cause shared mutable state issues if the tuple is mistakenly treated as mutable, though tuples are immutable—this hazard is low but included for completeness regarding developer assumption.
- In code that relies on the set literal being evaluated as a new object each time (e.g., in a loop or function call), the tuple rewrite also creates a new object each time, so no behavioral change occurs from object identity alone, but any reliance on set-specific identity (e.g., 'is' checks against a known set) would fail.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | invalid | String Formatting Edge Cases | When iterating over a set literal used in string formatting via % or .format(), replacement with a tuple changes argument order, affecting formatted output if placeholders rely on positional matching. |
| r1-p002 | fix ok | Loop Variable Mutation and Set Reconstruction | Mutating the loop variable during iteration and using it to rebuild a set can yield different element orders depending on whether the source was a set (arbitrary order) or tuple (fixed order), affecti |
| r1-p003 | silent prediction off | Star Imports, Module-Level Side Effects, __all__ | If a module's __all__ is defined by iterating over a set literal of strings, replacing it with a tuple changes the order of __all__, affecting star-import symbol resolution order and potentially which |
| r1-p004 | silent prediction off | Evaluation Order and Side Effects | When the set literal contains expressions with side effects (e.g., print or list append), duplicating or reordering those expressions in the tuple rewrite changes the number or order of side effects. |
| r1-p005 | invalid | Hash Randomization and Determinism | Although string and int literals have stable hash, this program simulates sensitivity by using a placeholder; the hazard is theoretical: if set iteration order varied across runs due to hash seeding, |
| r1-p006 | silent prediction off | Dict Ordering, Duplicate Keys, __hash__/__eq__ Overrides | When the set contains custom objects with overridden __hash__ and __eq__, iteration order affects the sequence in which they are processed, which may matter when used as dict keys or in further set op |
| r1-p007 | invalid | File Modes and Truncation | Iterating over a set of file mode strings to open and write files: if the set is replaced by a tuple, the order of file creation/changes is fixed, potentially altering which file gets written first in |
| r1-p008 | silent prediction off | Set Object Identity and 'is' Checks | If code relies on the set literal being a fresh object each time (e.g., for identity checks via 'is'), replacing it with a tuple does not preserve the object type, causing 'is' comparisons against a s |
| r2-p001 | invalid | Walrus assignment captures set identity before rewrite | walrus, comprehensions scoping, late binding in closures and lambdas |
| r2-p002 | silent prediction off | Set literal in union expression broken by tuple replacement | When the set literal is part of a larger expression (e.g., union or intersection with another set), replacing it with a tuple breaks the operation as tuples do not support set methods. |
| r2-p003 | invalid | Inline comment lost during set-to-tuple rewrite | Comments attached to the set literal (e.g., {1, 2 # inline}) may be lost or misplaced during the rewrite, as the autofix typically does not preserve comment placement within the literal. |
| r2-p004 | invalid | Context manager relying on fresh set object identity | context managers that are not files, objects with write() but no close() |
| r2-p005 | silent prediction off | Type annotation evaluated at runtime from set literal | type annotations evaluated at runtime, typing-only imports |
| r2-p006 | fix ok | __all__ built from set literal triggers module-level side effect | star-imports, module-level side effects, __all__ |
| r2-p007 | invalid | Pathlib behavior depends on set iteration order | pathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths |
| r2-p008 | fix ok | Exception in loop body with finally clause affected by iteration order | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
Sources
r1-p001 · String Formatting Edge Cases (generated program is not deterministic)
1def fmt_set(s):
2 return '%s:%s' % tuple(s)
3
4print(fmt_set({'a', 'b'}))
r1-p002 · Loop Variable Mutation and Set Reconstruction
1items = []
2for x in {'a', 'b'}:
3 items.append(x.upper())
4rebuilt = set(items)
5print(''.join(sorted(rebuilt)))
--- orig.py 2026-10-17 16:57:39.421610644 -0400 +++ fixed.py 2026-10-17 16:57:39.587612913 -0400 @@ -1,5 +1,5 @@ items = [] -for x in {'a', 'b'}: +for x in ('a', 'b'): items.append(x.upper()) rebuilt = set(items) print(''.join(sorted(rebuilt)))
AB
r1-p003 · Star Imports, Module-Level Side Effects, __all__
1__all__ = tuple(sorted({'b', 'a'}))
2print(__all__[0])
a
r1-p004 · Evaluation Order and Side Effects
1effects = []
2def f(x):
3 effects.append(x)
4 return x
5for _ in {f(1), f(2)}:
6 pass
7print(effects)
[1, 2]
r1-p005 · Hash Randomization and Determinism (generated program is not deterministic)
1data = {'a', 'b'}
2result = list(data)
3print(''.join(result))
r1-p006 · Dict Ordering, Duplicate Keys, __hash__/__eq__ Overrides
1class Obj:
2 def __init__(self, v): self.v = v
3 def __hash__(self): return hash(self.v)
4 def __eq__(self, other): return isinstance(other, Obj) and self.v == other.v
5 def __repr__(self): return f'Obj({self.v})'
6s = {Obj(1), Obj(2)}
7print([obj.v for obj in s])
[1, 2]
r1-p007 · File Modes and Truncation (generated program is not deterministic)
1for mode in {'w', 'a'}:
2 with open(f'test_{mode}.txt', mode) as f:
3 f.write(mode)
4 with open(f'test_{mode}.txt', 'r') as f:
5 print(f.read(), end='')
r1-p008 · Set Object Identity and 'is' Checks
1s = {'a', 'b'}
2print(s is {'a', 'b'})
False
r2-p001 · Walrus assignment captures set identity before rewrite (generated program does not parse: Traceback (most recent call last):)
1def f():
2 s = None
3 for x in ({} := {1, 2}):
4 s.add(x) if s else s := {x}
5 return len(s)
6print(f())
r2-p002 · Set literal in union expression broken by tuple replacement
1for x in {1, 2} | {3}:
2 print(x, end='')
3print()
123
r2-p003 · Inline comment lost during set-to-tuple rewrite (generated program does not parse: Traceback (most recent call last):)
1for x in {1, 2 # first two}:
2 print(f'{x}#')
r2-p004 · Context manager relying on fresh set object identity (generated program is not deterministic)
1class Ctx:
2 def __init__(self, obj):
3 self.obj = obj
4 self.id = id(obj)
5 def __enter__(self):
6 print(f'enter {self.id}')
7 return self
8 def __exit__(self, *e):
9 print(f'exit {self.id}')
10with Ctx({1, 2}) as c:
11 pass
r2-p005 · Type annotation evaluated at runtime from set literal
1T = {1, 2}
2def f(x: T) -> int:
3 return x
4try:
5 print(f(1))
6except Exception as e:
7 print(type(e).__name__, e)
1
r2-p006 · __all__ built from set literal triggers module-level side effect
1__all__ = [x for x in {'a', 'b'}]
2print(__all__)
--- orig.py 2026-10-17 16:57:47.412719610 -0400 +++ fixed.py 2026-10-17 16:57:47.581721909 -0400 @@ -1,2 +1,2 @@ -__all__ = [x for x in {'a', 'b'}] +__all__ = [x for x in ('a', 'b')] print(__all__)
['a', 'b']
r2-p007 · Pathlib behavior depends on set iteration order (generated program is not deterministic)
1import os
2from pathlib import Path
3for p in {Path('a'), Path('b')}:
4 p.write_text(p.name)
5 print(p.read_text().strip())
6for p in {Path('a'), Path('b')}:
7 p.unlink()
r2-p008 · Exception in loop body with finally clause affected by iteration order
1for x in {1, 2}:
2 try:
3 if x == 2:
4 raise ValueError(x)
5 print(x)
6 finally:
7 print(f'finally {x}')
--- orig.py 2026-10-17 16:57:47.804724942 -0400 +++ fixed.py 2026-10-17 16:57:48.193730232 -0400 @@ -1,4 +1,4 @@ -for x in {1, 2}: +for x in (1, 2): try: if x == 2: raise ValueError(x)
1 finally 1 finally 2