ruff C408
labelled unknown
Analysis Nemotron 3 Super
Flags unnecessary dict(), list(), or tuple() calls without arguments that can be replaced by empty literal syntax.
Fix: Replaces dict() with {}, list() with [], and tuple() with () when called with no arguments.
9 hazards
- Rewriting dict() to {} may drop inline comments attached to the call expression if they are not preserved during AST transformation.
- If dict is locally rebound (e.g., `dict = lambda: None`), replacing dict() with {} changes semantics because the literal invokes the built-in dict type, not the local binding.
- In rare cases involving import hooks or metaprogramming that intercept built-in name resolution, replacing dict() with {} bypasses the hook, altering behavior.
- If the code relies on the traceback showing a call to 'dict()' for debugging or logging, replacing it with {} removes that call frame, affecting diagnostic output.
- When dict() appears in a string literal or comment (e.g., 'print("dict()")'), the autofix must not trigger, but a buggy implementation might erroneously modify such strings.
- If dict() is used in a context where its return value is intentionally ignored for side effects (though dict() has none), replacing it with {} could mislead readers about intent, though no behavioral change occurs.
- In code that is parsed and then executed via exec() or eval() with a restricted globals where dict is removed, replacing dict() with {} may succeed where the original would raise NameError, altering error behavior.
- If the code depends on the exact bytecode or memory allocation pattern of calling dict() vs. using {} (e.g., for benchmarking or low-level optimization), the rewrite changes those characteristics.
- When dict() appears in a DSL or code-generation context where the text 'dict()' is later interpreted by another system, replacing it with {} breaks downstream processing.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | Coercion with nested constructors | int/float/bool coercion, Decimal, division and rounding |
| r1-p002 | fired, no fix | Async generator exhaustion timing | async def/await ordering, async generators |
| r1-p003 | silent prediction off | Shadowed dict then restored global call | shadowed builtins and names redefined later in the module |
| r1-p004 | fired, no fix | Partial write from constructor reuse | file modes and truncation, partial writes, non-str data passed to write |
| r1-p005 | fired, no fix | Walrus in comprehension capture | walrus, comprehensions scoping, late binding in closures and lambdas |
| r1-p006 | fired, no fix | Bytecode difference via disassembly | If the code depends on the exact bytecode or memory allocation pattern of calling dict() vs. using {} (e.g., for benchmarking or low-level optimization), the rewrite changes those characteristics. |
| r1-p007 | invalid | Duplicate key survival in literal vs call | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r1-p008 | fired, no fix | Side effect order in nested call | evaluation order and side effects when expressions are merged, duplicated or deleted |
| r2-p001 | silent prediction off | exec with restricted globals removing dict | In code that is parsed and then executed via exec() or eval() with a restricted globals where dict is removed, replacing dict() with {} may succeed where the original would raise NameError, altering e |
| r2-p002 | silent prediction off | class attribute descriptor with side effect | class attributes vs instance attributes, descriptors, properties with side effects |
| r2-p003 | fired, no fix | truthiness of custom __bool__/__len__ | truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' |
| r2-p004 | silent prediction off | local rebinding of dict | If dict is locally rebound (e.g., `dict = lambda: None`), replacing dict() with {} changes semantics because the literal invokes the built-in dict type, not the local binding. |
| r2-p005 | fired, no fix | inline comment preservation | Rewriting dict() to {} may drop inline comments attached to the call expression if they are not preserved during AST transformation. |
| r2-p006 | silent prediction off | async function interaction | async def/await ordering, async generators |
| r2-p007 | silent | pathlib vs os.path behavior | pathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths |
| r2-p008 | silent prediction off | generator consumption | generators vs lists, iterators consumed once, lazy vs eager evaluation |
Sources
r1-p001 · Coercion with nested constructors
1from decimal import Decimal
2x = float(int(round(float(dict()))))
3print(x)
r1-p002 · Async generator exhaustion timing
1async def gen():
2 yield await list().__repr__()
3async def main():
4 g = gen()
5 print(await g.__anext__())
6import asyncio
7asyncio.run(main())
r1-p003 · Shadowed dict then restored global call
1dict = lambda: {'shadow': True}
2x = dict()
3print(x)
4del dict
5x = dict()
6print(sorted(x.items()))
{'shadow': True}
[]r1-p004 · Partial write from constructor reuse
1with open('tmp.bin', 'wb') as f:
2 f.write(bytes(list()))
3with open('tmp.bin', 'rb') as f:
4 print(f.read())
5import os
6os.remove('tmp.bin')
b''
r1-p005 · Walrus in comprehension capture
1fns = []
2for i in range(2):
3 if (x := list()) is not None:
4 fns.append(lambda: len(x))
5print([f() for f in fns])
[0, 0]
r1-p006 · Bytecode difference via disassembly
1import dis
2print('dict() bytecode:')
3dis.dis(lambda: dict())
4print('{} bytecode:')
5dis.dis(lambda: {})
dict() bytecode:
3 RESUME 0
LOAD_GLOBAL 1 (dict + NULL)
CALL 0
RETURN_VALUE
{} bytecode:
5 RESUME 0
BUILD_MAP 0
RETURN_VALUEr1-p007 · Duplicate key survival in literal vs call (generated program is not deterministic)
1class BadHash:
2 def __hash__(self): return 1
3 def __eq__(self, o): return isinstance(o, BadHash)
4a = BadHash()
5b = BadHash()
6d = dict([(a, 1), (b, 2)])
7print(len(d), sorted(d.items()))
r1-p008 · Side effect order in nested call
1side = []
2def f():
3 side.append(1)
4 return dict()
5x = f()
6side.append(2)
7print(side, x)
[1, 2] {}r2-p001 · exec with restricted globals removing dict
1g = {'__builtins__': {}}
2exec("x = dict()", g)
3print(type(x))
r2-p002 · class attribute descriptor with side effect
1class Desc:
2 def __get__(self, obj, owner):
3 print('Desc.get')
4 return []
5
6class A:
7 attr = Desc()
8
9a = A()
10print(type(a.attr))
11a.attr.append(1)
12print(type(A.attr))
Desc.get <class 'list'> Desc.get Desc.get <class 'list'>
r2-p003 · truthiness of custom __bool__/__len__
1class X:
2 def __len__(self):
3 return 1
4
5x = list()
6print(bool(x))
7x.append(X())
8print(bool(x))
False True
r2-p004 · local rebinding of dict
1def f():
2 dict = lambda: None
3 x = dict()
4 y = {}
5 print(type(x), type(y))
6f()
<class 'NoneType'> <class 'dict'>
r2-p005 · inline comment preservation
1x = dict() # make empty
2print(type(x))
<class 'dict'>
r2-p006 · async function interaction
1async def g():
2 return []
3
4async def main():
5 x = await g()
6 print(type(x))
7
8import asyncio
9asyncio.run(main())
<class 'list'>
r2-p007 · pathlib vs os.path behavior
1import os
2import tempfile
3with tempfile.TemporaryDirectory() as d:
4 p = os.path.join(d, 'test')
5 with open(p, 'w') as f:
6 f.write('hi')
7 print(os.path.exists(p))
True
r2-p008 · generator consumption
1def gen():
2 yield 1
3 yield 2
4
5g = gen()
6print(list(g))
7print(list(g))
[1, 2] []