ruff UP044
labelled unknown
Analysis Nemotron 3 Super
Flags type annotations using Unpack[] from typing where PEP 646 star-expression syntax (*T) can be used instead.
Fix: Replaces Unpack[X] with *X in type annotations, preserving the surrounding syntactic context (e.g., *args: *tuple[int, ...]).
8 hazards
- Rewriting Unpack[expr] to *expr changes the meaning if expr is not a type but a value, as *expr becomes an illegal starred expression outside function parameters or invalid syntax elsewhere.
- If Unpack appears in a string annotation (e.g., "Unpack[T]"), the fix may incorrectly modify it, breaking forward references or causing syntax errors in evaluated annotations.
- In nested generic contexts like Dict[str, Unpack[Tuple[int]]], rewriting to *Tuple[int] produces invalid syntax because * is not allowed inside bracket notation.
- When Unpack is used as a variable name (e.g., Unpack = 5), the fix erroneously rewrites it to *5, which is invalid syntax and alters the identifier's meaning.
- The fix may remove or misplace comments attached to the Unpack token (e.g., Unpack[# inline comment int]) due to token-based replacement without comment preservation.
- In class bodies or module-level code, *expr is syntactically invalid unless in a function parameter list, so rewriting Unpack[T] there raises a SyntaxError.
- If the code targets Python <3.11, using *T in type annotations may be unsupported by the interpreter or type checkers, even if semantically equivalent.
- When Unpack is imported under an alias (e.g., from typing import Unpack as U), the fix may fail to trigger or incorrectly rewrite the alias, leading to inconsistent or missed transformations.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Generator consumption with Unpack | generators vs lists, iterators consumed once, lazy vs eager evaluation |
| r1-p002 | silent | Unpack as variable shadowed by rewrite | When Unpack is used as a variable name, the fix rewrites it to *5, causing syntax error |
| r1-p003 | silent prediction off | Descriptor interaction with unpacked type annotation | class attributes vs instance attributes, descriptors, properties with side effects |
| r1-p004 | silent prediction off | Side effects in duplicated unpack expression | evaluation order and side effects when expressions are merged, duplicated or deleted |
| r1-p005 | silent prediction off | Python <3.11 compatibility with *T syntax | If the code targets Python <3.11, using *T in type annotations may be unsupported |
| r1-p006 | silent prediction off | Walrus and scoping with unpacked lambda | walrus, comprehensions scoping, late binding in closures and lambdas |
| r1-p007 | silent prediction off | Mutable default and aliasing after unpack rewrite | mutable default arguments and aliasing after a rewrite |
| r1-p008 | silent prediction off | Exception handling in unpacked function call | exceptions raised inside the rewritten code, finally/else clauses, exception chaining |
| r2-p001 | invalid | Walrus, comprehensions, closures | walrus, comprehensions scoping, late binding in closures and lambdas |
| r2-p002 | silent prediction off | Comments on Unpack token | The fix may remove or misplace comments attached to the Unpack token |
| r2-p003 | silent | Pathlib vs os.path behavior | pathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths |
| r2-p004 | fired, no fix | Aliased Unpack import | When Unpack is imported under an alias, the fix may fail to trigger or incorrectly rewrite the alias |
| r2-p005 | silent prediction off | Truthiness and identity checks | truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' |
| r2-p006 | fired, no fix | Evaluation order and side effects | evaluation order and side effects when expressions are merged, duplicated or deleted |
| r2-p007 | silent prediction off | String annotations | When Unpack appears in a string annotation, the fix may incorrectly modify it |
| r2-p008 | silent prediction off | Dict ordering and overrides | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
Sources
r1-p001 · Generator consumption with Unpack
1from typing import Unpack
2def gen(): yield 1; yield 2
3values = gen()
4def f(x: Unpack[tuple[int, int]]): return sum(x)
5print(f(*values))
6try:
7 print(sum(values))
8except TypeError as e:
9 print('generator exhausted:', e)
r1-p002 · Unpack as variable shadowed by rewrite
1Unpack = 5
2def f(x: int): return x + Unpack
3print(f(1))
6
r1-p003 · Descriptor interaction with unpacked type annotation
1from typing import Unpack
2class Desc:
3 def __get__(self, obj, owner): return 99
4class C:
5 x = Desc()
6 def __init__(self, a: Unpack[tuple[int]]): self.y = self.x + a[0]
7c = C((5,))
8print(c.y)
104
r1-p004 · Side effects in duplicated unpack expression
1from typing import Unpack
2side = 0
3def get_values():
4 global side
5 side += 1
6 return (side, side*2)
7def f(x: Unpack[tuple[int, int]]): return x[0] + x[1]
8print(f(*get_values()))
9print('side effect count:', side)
r1-p005 · Python <3.11 compatibility with *T syntax
1import sys
2from typing import Unpack
3if sys.version_info < (3, 11):
4 print('skipping: Python <3.11')
5else:
6 def f(x: Unpack[tuple[int, int]]): return x[0] * 2
7 print(f(3, 4))
r1-p006 · Walrus and scoping with unpacked lambda
1from typing import Unpack
2vals = [1, 2]
3f = lambda *args: (n := len(args)) + sum(args)
4print(f(*vals))
5print('n leaked?', 'n' in locals())
5 n leaked? False
r1-p007 · Mutable default and aliasing after unpack rewrite
1from typing import Unpack
2def f(x: Unpack[tuple[int, ...]] = (), lst=[]):
3 lst.extend(x)
4 return lst
5print(f(1, 2))
6print(f(3))
7print(f())
r1-p008 · Exception handling in unpacked function call
1from typing import Unpack
2def f(x: Unpack[tuple[int, int]]):
3 if x[0] == 0: raise ValueError('zero')
4 return x[1] // x[0]
5try:
6 print(f(0, 5))
7except ValueError as e:
8 print('caught:', e)
9else:
10 print('no error')
11finally:
12 print('cleanup')
cleanup
r2-p001 · Walrus, comprehensions, closures (generated program does not parse: Traceback (most recent call last):)
1from typing import Unpack
2n = 3
3data = [(i,) for i in range(n)]
4fns = [lambda *x=*t: sum(x) for t in data]
5print([f() for f in fns])
r2-p002 · Comments on Unpack token
1from typing import Unpack
2def f(x: Unpack[ # comment
3 tuple[int, str]]) -> None:
4 pass
5f((1, 2))
6print('ok')
ok
r2-p003 · Pathlib vs os.path behavior
1import os
2try:
3 open('missing.txt').close()
4except FileNotFoundError as e:
5 print(e)
[Errno 2] No such file or directory: 'missing.txt'
r2-p004 · Aliased Unpack import
1from typing import Unpack as U
2def f(*args: U[tuple[int, ...]]) -> int:
3 return sum(args)
4print(f(1, 2, 3))
6
r2-p005 · Truthiness and identity checks
1from typing import Unpack
2class A:
3 def __bool__(self): return False
4 def __len__(self): return 1
5a = A()
6def f(x: Unpack[tuple[A, ...]]) -> bool:
7 return all(x)
8print(f(a))
r2-p006 · Evaluation order and side effects
1from typing import Unpack
2side = []
3def f():
4 side.append(1)
5 return (2,)
6def g(*args: Unpack[tuple[int, ...]]):
7 return side.pop() if side else 0
8print(g(f()))
1
r2-p007 · String annotations
1from typing import Unpack, get_type_hints
2def f(*args: 'Unpack[tuple[int, ...]]') -> None:
3 pass
4hints = get_type_hints(f)
5print(hints.get('args', 'missing'))
typing.Unpack[tuple[int, ...]]
r2-p008 · Dict ordering and overrides
1from typing import Unpack
2class B:
3 def __init__(self, v): self.v = v
4 def __hash__(self): return hash(self.v)
5 def __eq__(self, other): return self.v == other.v
6b1, b2 = B(1), B(2)
7def f(**kw: Unpack[dict[str, B]]) -> None:
8 print(list(kw.keys()))
9f(b1=b1, b2=b2)
['b1', 'b2']