fixhunt

ruff UP044

labelled unknown
Runs / 2026-10-17T21-26-50-279Z-akfq-ruff-UP044ruff 0.17.12026-10-17 21:26 UTC8 programs × 2 round(s)16 programs, 2 fired, 0 fixed, 0 findings, 1 rejectedmodel cost $0.0029

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

idresulttitlehazard
r1-p001silent prediction offGenerator consumption with Unpackgenerators vs lists, iterators consumed once, lazy vs eager evaluation
r1-p002silentUnpack as variable shadowed by rewriteWhen Unpack is used as a variable name, the fix rewrites it to *5, causing syntax error
r1-p003silent prediction offDescriptor interaction with unpacked type annotationclass attributes vs instance attributes, descriptors, properties with side effects
r1-p004silent prediction offSide effects in duplicated unpack expressionevaluation order and side effects when expressions are merged, duplicated or deleted
r1-p005silent prediction offPython <3.11 compatibility with *T syntaxIf the code targets Python <3.11, using *T in type annotations may be unsupported
r1-p006silent prediction offWalrus and scoping with unpacked lambdawalrus, comprehensions scoping, late binding in closures and lambdas
r1-p007silent prediction offMutable default and aliasing after unpack rewritemutable default arguments and aliasing after a rewrite
r1-p008silent prediction offException handling in unpacked function callexceptions raised inside the rewritten code, finally/else clauses, exception chaining
r2-p001invalidWalrus, comprehensions, closureswalrus, comprehensions scoping, late binding in closures and lambdas
r2-p002silent prediction offComments on Unpack tokenThe fix may remove or misplace comments attached to the Unpack token
r2-p003silentPathlib vs os.path behaviorpathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths
r2-p004fired, no fixAliased Unpack importWhen Unpack is imported under an alias, the fix may fail to trigger or incorrectly rewrite the alias
r2-p005silent prediction offTruthiness and identity checkstruthiness of custom __bool__/__len__, None vs falsy, 'is' vs '=='
r2-p006fired, no fixEvaluation order and side effectsevaluation order and side effects when expressions are merged, duplicated or deleted
r2-p007silent prediction offString annotationsWhen Unpack appears in a string annotation, the fix may incorrectly modify it
r2-p008silent prediction offDict ordering and overridesdict 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']