fixhunt

ruff TC006

labelled unknown
Runs / 2026-10-17T20-41-40-602Z-mapj-ruff-TC006ruff 0.17.12026-10-17 20:41 UTC8 programs × 2 round(s)16 programs, 14 fired, 14 fixed, 2 findings, 0 rejectedmodel cost $0.04

Analysis Nemotron 3 Super

The rule flags unquoted type expressions in typing.cast() calls.

Fix: Wrap the first argument of typing.cast() in double quotes if it is not already a string literal, preserving whitespace and formatting.

11 hazards
  • If the first argument is a variable name (e.g., cast(T, x)), quoting it changes it from a type reference to a string literal, causing a runtime error when the type checker expects a type but receives a string.
  • If the first attribute is a class or type imported locally (not from typing), quoting it prevents actual type resolution at runtime, breaking isinstance or issubclass checks that might rely on the cast target.
  • If the type expression spans multiple lines (even without comments), the autofix may incorrectly quote only the first line or fail to preserve line breaks, resulting in a syntax error.
  • If the first argument contains a comment on any line except the last, the fix may be applied incorrectly or omitted by Ruff due to its safety condition, but if forced, could comment out part of the type expression.
  • If the cast call uses keyword arguments like cast(typ=MyType, val=x), the autofix may not recognize the structure and either misapply quotes or fail to fire, leading to inconsistent fixes.
  • If the type expression contains a forward reference that is only resolved during TYPE_CHECKING, quoting it is correct, but if the same expression appears in a runtime context elsewhere, the inconsistency may mask circular import issues.
  • If the type expression is a string literal already but uses single quotes (e.g., cast('int', x)), the rule may not fire (depending on configuration), but if it did and the fix added outer quotes, it would produce a malformed nested string like cast(''int'', x).
  • If the code relies on the type expression being evaluated for side effects (e.g., a function call that returns a type), quoting it prevents execution, removing those side effects and potentially altering program state.
  • If the cast is inside a lambda or comprehension and the type expression references a loop variable, quoting it changes the meaning from a dynamic type lookup to a static string, breaking intended behavior.
  • If the type expression uses the | union operator (Python 3.10+) like cast(int | str, x), quoting it is necessary for forward compatibility, but if the code is run on Python <3.10, the unquoted version would already fail, so the fix doesn't introduce new breakage but assumes the target environment supports the syntax.
  • If the first argument is a tuple or other container mistakenly used as a type (e.g., cast((int, str), x)), quoting it hides the fact that it's not a valid type annotation, delaying detection of a logical error until type checking fails.

Findings

programoraclelabeljudgestatussummary
r1-p003fix-changes-behaviorlabelled safereal · highrelatedThe rule quotes the first argument of typing.cast() to enforce stylistic consistency, but the program uses a forward reference (T is assigned later). Quoting '…
r2-p003fix-changes-behaviorlabelled safereal · highrelatedThe fix quotes the type expression 'MyType', changing it from a runtime-evaluated expression to a forward-reference string. This alters the evaluation order: o…

Programs

idresulttitlehazard
r1-p001fix okVariable as type expressionIf the first argument is a variable name (e.g., cast(T, x)), quoting it changes it from a type reference to a string literal, causing a runtime error when the type checker expects a type but receives
r1-p002fix okMultiline type expressionIf the type expression spans multiple lines (even without comments), the autofix may incorrectly quote only the first line or fail to preserve line breaks, resulting in a syntax error.
r1-p003fix-changes-behaviorShadowed builtins and redefined namesshadowed builtins and names redefined later in the module
r1-p004fix okAsync def/await orderingasync def/await ordering, async generators
r1-p005fix okKeyword arguments in castIf the cast call uses keyword arguments like cast(typ=MyType, val=x), the autofix may not recognize the structure and either misapply quotes or fail to fire, leading to inconsistent fixes.
r1-p006fix okTuple mistaken for typeIf the first argument is a tuple or other container mistakenly used as a type (e.g., cast((int, str), x)), quoting it hides the fact that it's not a valid type annotation, delaying detection of a logi
r1-p007silentSingle quotes already usedIf the type expression is a string literal already but uses single quotes (e.g., cast('int', x)), the rule may not fire (depending on configuration), but if it did and the fix added outer quotes, it w
r1-p008fix okComments and docstrings attachedcomments and docstrings attached to the removed or moved code
r2-p001fix okContext manager with __enter__ returning non-selfcontext managers that are not files, objects with write() but no close()
r2-p002fix okFloat division and rounding with castint/float/bool coercion, Decimal, division and rounding
r2-p003fix-changes-behaviorForward reference in TYPE_CHECKING vs runtimeIf the type expression contains a forward reference that is only resolved during TYPE_CHECKING, quoting it is correct, but if the same expression appears in a runtime context elsewhere, the inconsiste
r2-p004fix okUnion using | operator in castIf the type expression uses the | union operator (Python 3.10+) like cast(int | str, x), quoting it is necessary for forward compatibility, but if the code is run on Python <3.10, the unquoted version
r2-p005fix okF-string nested quotes in cast argumentstring formatting edge cases: %, format, f-strings, repr vs str, nested quotes
r2-p006fix okCast with inline comment after argumentcomments and docstrings attached to the removed or moved code
r2-p007silentSingle-quoted string literal already in castIf the type expression is a string literal already but uses single quotes (e.g., cast('int', x)), the rule may not fire (depending on configuration), but if it did and the fix added outer quotes, it w
r2-p008fix okMutable default argument and aliasingmutable default arguments and aliasing after a rewrite

Sources

r1-p001 · Variable as type expression
1from typing import cast, List
2T = List[int]
3def main():
4 x = [1, 2, 3]
5 try:
6 # This should work: T is a type
7 y = cast(T, x)
8 print('Before fix:', len(y))
9 except Exception as e:
10 print('Error before fix:', type(e).__name__, str(e))
11 # After fix: cast('T', x) -> T becomes string literal, not a type
12 # This will cause mypy/pyright to fail, but at runtime it's still a cast
13 # However, if we later use y as a type (e.g. in isinstance), it breaks
14 try:
15 if isinstance(y, T): # T is still List[int] here
16 print('y is instance of T')
17 else:
18 print('y is not instance of T')
19 except Exception as e:
20 print('Error in isinstance:', type(e).__name__, str(e))
21if __name__ == '__main__':
22 main()
--- orig.py	2026-10-17 16:42:02.145379343 -0400
+++ fixed.py	2026-10-17 16:42:02.330385991 -0400
@@ -4,7 +4,7 @@
     x = [1, 2, 3]
     try:
         # This should work: T is a type
-        y = cast(T, x)
+        y = cast('T', x)
         print('Before fix:', len(y))
     except Exception as e:
         print('Error before fix:', type(e).__name__, str(e))
Before fix: 3
Error in isinstance: TypeError Subscripted generics cannot be used with class and instance checks
r1-p002 · Multiline type expression
1from typing import cast, List, Dict
2# Type expression split across lines
3MyType = Dict[
4 str,
5 List[int]
6]
7def main():
8 data = {'a': [1, 2]}
9 try:
10 # This cast has a multiline first argument
11 result = cast(
12 MyType,
13 data
14 )
15 print('Success:', result['a'])
16 except Exception as e:
17 print('Error during cast:', type(e).__name__, str(e))
18 # After fix: the autofix might produce:
19 # cast(
20 # "MyType,
21 # \n str,
22 # \n List[int]
23 # ), data
24 # which is invalid syntax due to broken string
25 # We simulate by trying to eval the broken form
26 try:
27 broken = 'cast(\n "MyType,\n \nstr,\n \nList[int]\n), data'
28 # This would be a syntax error if parsed
29 print('Broken form would fail to parse')
30 except:
31 pass
32if __name__ == '__main__':
33 main()
--- orig.py	2026-10-17 16:42:02.436389801 -0400
+++ fixed.py	2026-10-17 16:42:02.624396557 -0400
@@ -9,7 +9,7 @@
     try:
         # This cast has a multiline first argument
         result = cast(
-            MyType,
+            'MyType',
             data
         )
         print('Success:', result['a'])
Success: [1, 2]
Broken form would fail to parse
r1-p003 · Shadowed builtins and redefined names
1from typing import cast, List
2list = str # Shadow builtin
3T = list # Now T is str
4def main():
5 x = 'hello'
6 # First use: T refers to shadowed list (which is str)
7 y = cast(T, x) # Should be cast(str, x)
8 print('After cast:', y.upper() if isinstance(y, str) else 'fail')
9 # Later, redefine list back to builtin
10 del list # Restore builtin
11 list = lambda x: x # Redefine as identity
12 T = list # Now T is the lambda
13 z = cast(T, x) # cast(lambda, x) -> invalid type
14 try:
15 # This won't fail at runtime, but type checker will complain
16 print('z:', z)
17 except Exception as e:
18 print('Error after redefinition:', type(e).__name__, str(e))
19if __name__ == '__main__':
20 main()
--- orig.py	2026-10-17 16:42:02.732400438 -0400
+++ fixed.py	2026-10-17 16:42:03.133414842 -0400
@@ -4,13 +4,13 @@
 def main():
     x = 'hello'
     # First use: T refers to shadowed list (which is str)
-    y = cast(T, x)  # Should be cast(str, x)
+    y = cast('T', x)  # Should be cast(str, x)
     print('After cast:', y.upper() if isinstance(y, str) else 'fail')
     # Later, redefine list back to builtin
     del list  # Restore builtin
     list = lambda x: x  # Redefine as identity
     T = list  # Now T is the lambda
-    z = cast(T, x)  # cast(lambda, x) -> invalid type
+    z = cast('T', x)  # cast(lambda, x) -> invalid type
     try:
         # This won't fail at runtime, but type checker will complain
         print('z:', z)
r1-p004 · Async def/await ordering
1from typing import cast, List, AsyncIterator
2import asyncio
3async def gen() -> AsyncIterator[int]:
4 for i in range(3):
5 yield i
6async def main():
7 # cast in async context
8 g = gen()
9 ag = cast(AsyncIterator[int], g) # Rule fires here
10 try:
11 result = []
12 async for item in ag:
13 result.append(item)
14 print('Collected:', result)
15 except Exception as e:
16 print('Error in async iteration:', type(e).__name__, str(e))
17 # After fix: cast('AsyncIterator[int]', g) -> string, not type
18 # This breaks type checking but not runtime
19 # However, if we try to use ag as a type later...
20 try:
21 if isinstance(g, AsyncIterator): # Name not defined, but we meant the type
22 pass
23 except NameError:
24 print('Expected: AsyncIterator not found in runtime')
25asyncio.run(main())
--- orig.py	2026-10-17 16:42:03.355422809 -0400
+++ fixed.py	2026-10-17 16:42:03.678434400 -0400
@@ -6,7 +6,7 @@
 async def main():
     # cast in async context
     g = gen()
-    ag = cast(AsyncIterator[int], g)  # Rule fires here
+    ag = cast('AsyncIterator[int]', g)  # Rule fires here
     try:
         result = []
         async for item in ag:
Collected: [0, 1, 2]
r1-p005 · Keyword arguments in cast
1from typing import cast, List
2def main():
3 x = [1, 2, 3]
4 # Keyword form: should trigger rule on typ=
5 y = cast(typ=List[int], val=x)
6 print('Length via keyword:', len(y))
7 # After fix: autofix might not recognize keyword args and do nothing
8 # Or worse, quote the first positional arg (which doesn't exist)
9 # Simulate bad fix: if it tried to quote 'typ' or messed up keywords
10 try:
11 # This is safe: keyword args are preserved
12 z = cast(typ=List[int], val=[4, 5])
13 print('Second call:', z)
14 except Exception as e:
15 print('Error in keyword cast:', type(e).__name__, str(e))
16 # To show dependency: if we change List[int] to a var, and fix breaks
17 T = List[int]
18 # Rule should fire on typ=T
19 w = cast(typ=T, val=x)
20 print('Via var:', len(w))
21if __name__ == '__main__':
22 main()
--- orig.py	2026-10-17 16:42:03.856440788 -0400
+++ fixed.py	2026-10-17 16:42:04.048447675 -0400
@@ -2,21 +2,21 @@
 def main():
     x = [1, 2, 3]
     # Keyword form: should trigger rule on typ=
-    y = cast(typ=List[int], val=x)
+    y = cast(typ='List[int]', val=x)
     print('Length via keyword:', len(y))
     # After fix: autofix might not recognize keyword args and do nothing
     # Or worse, quote the first positional arg (which doesn't exist)
     # Simulate bad fix: if it tried to quote 'typ' or messed up keywords
     try:
         # This is safe: keyword args are preserved
-        z = cast(typ=List[int], val=[4, 5])
+        z = cast(typ='List[int]', val=[4, 5])
         print('Second call:', z)
     except Exception as e:
         print('Error in keyword cast:', type(e).__name__, str(e))
     # To show dependency: if we change List[int] to a var, and fix breaks
     T = List[int]
     # Rule should fire on typ=T
-    w = cast(typ=T, val=x)
+    w = cast(typ='T', val=x)
     print('Via var:', len(w))
 if __name__ == '__main__':
     main()
Length via keyword: 3
Second call: [4, 5]
Via var: 3
r1-p006 · Tuple mistaken for type
1from typing import cast
2# Mistakenly using tuple as type - should be flagged by linter but isn't a type
3def main():
4 x = (1, 'hello')
5 # This is wrong: (int, str) is a tuple, not a type
6 # Rule TC006 should fire because it's unquoted
7 y = cast((int, str), x) # Expect trigger: first arg is tuple literal
8 print('Cast result:', y, type(y))
9 # At runtime, this succeeds because cast ignores the type argument
10 # But if we later try to use (int, str) as a type in isinstance, it fails
11 try:
12 if isinstance(y, (int, str)): # This works: tuple of types
13 print('isinstance check passed')
14 else:
15 print('isinstance check failed')
16 except Exception as e:
17 print('Error in isinstance:', type(e).__name__, str(e))
18 # After fix: cast('(int, str)', x) -> first arg becomes string
19 # Now, if code later tried to evaluate the string as a type (e.g. via eval), it would fail
20 # But more importantly, the linter fix masks the error: the user thinks it's quoted on purpose
21 # So they don't realize (int, str) was never a valid type
22 try:
23 eval_type = eval('(int, str)') # This would work
24 print('Eval succeeded:', eval_type)
25 except Exception as e:
26 print('Eval error:', type(e).__name__, str(e))
27if __name__ == '__main__':
28 main()
--- orig.py	2026-10-17 16:42:04.163451796 -0400
+++ fixed.py	2026-10-17 16:42:04.347458390 -0400
@@ -4,7 +4,7 @@
     x = (1, 'hello')
     # This is wrong: (int, str) is a tuple, not a type
     # Rule TC006 should fire because it's unquoted
-    y = cast((int, str), x)  # Expect trigger: first arg is tuple literal
+    y = cast('int, str', x)  # Expect trigger: first arg is tuple literal
     print('Cast result:', y, type(y))
     # At runtime, this succeeds because cast ignores the type argument
     # But if we later try to use (int, str) as a type in isinstance, it fails
Cast result: (1, 'hello') <class 'tuple'>
isinstance check failed
Eval succeeded: (<class 'int'>, <class 'str'>)
r1-p007 · Single quotes already used
1from typing import cast
2# Already quoted with single quotes - rule may not fire (config-dependent)
3def main():
4 x = 42
5 # This uses single quotes - depending on ruff config, TC006 may or may not trigger
6 # We assume it does NOT trigger (as per rule description: stays silent on single quotes)
7 y = cast('int', x) # No trigger expected
8 print('Value:', y, type(y))
9 # If the rule incorrectly fired and added outer quotes:
10 # cast(''int'', x) -> invalid syntax
11 # We simulate the broken code
12 try:
13 # This line is commented out to avoid syntax error in our program
14 # z = cast(''int'', x) # SyntaxError: invalid syntax
15 pass
16 except SyntaxError as e:
17 print('Simulated broken fix would cause:', type(e).__name__, str(e))
18 # Show that single-quoted string is fine at runtime
19 if isinstance(y, int):
20 print('Single-quoted cast worked as expected')
21if __name__ == '__main__':
22 main()
Value: 42 <class 'int'>
Single-quoted cast worked as expected
r1-p008 · Comments and docstrings attached
1from typing import cast, List
2# This comment is near the cast
3def process(items):
4 """Process a list of integers."""
5 # Cast with trailing comment
6 result = cast(List[int], items) # Ensure it's ints
7 # Another comment
8 total = 0
9 for x in result:
10 total += x
11 return total
12
13def main():
14 data = [1, 2, 3]
15 try:
16 output = process(data)
17 print('Total:', output)
18 except Exception as e:
19 print('Error in process:', type(e).__name__, str(e))
20 # After fix: the autofix might move or rewrite the line, potentially
21 # detaching the comment "Ensure it's ints" or the docstring
22 # If the comment is lost, runtime is unaffected, but intent is obscured
23 # We show that removing the comment doesn't change behavior
24 # But if the fix broke the line structure...
25 try:
26 # Simulate if fix broke indentation or moved line incorrectly
27 # Not actually breaking code, just noting hazard
28 print('Comment preservation affects readability, not runtime')
29 except:
30 pass
31if __name__ == '__main__':
32 main()
--- orig.py	2026-10-17 16:42:04.617468065 -0400
+++ fixed.py	2026-10-17 16:42:04.798474551 -0400
@@ -3,7 +3,7 @@
 def process(items):
     """Process a list of integers."""
     # Cast with trailing comment
-    result = cast(List[int], items)  # Ensure it's ints
+    result = cast('List[int]', items)  # Ensure it's ints
     # Another comment
     total = 0
     for x in result:
Total: 6
Comment preservation affects readability, not runtime
r2-p001 · Context manager with __enter__ returning non-self
1import typing
2class Ctx:
3 def __enter__(self):
4 return 42
5 def __exit__(self, *exc):
6 pass
7def f():
8 with Ctx() as c:
9 x = typing.cast(list[int], [c])
10 return sum(x)
11print(f())
--- orig.py	2026-10-17 16:42:10.848690314 -0400
+++ fixed.py	2026-10-17 16:42:11.033696885 -0400
@@ -6,6 +6,6 @@
         pass
 def f():
     with Ctx() as c:
-        x = typing.cast(list[int], [c])
+        x = typing.cast("list[int]", [c])
         return sum(x)
 print(f())
42
r2-p002 · Float division and rounding with cast
1import typing
2from decimal import Decimal
3def g():
4 a = 10
5 b = 3
6 res = typing.cast(float, a / b)
7 rounded = round(res, 2)
8 d = Decimal(str(res))
9 print(f'{res:.2f}', rounded, d)
10g()
--- orig.py	2026-10-17 16:42:11.163701496 -0400
+++ fixed.py	2026-10-17 16:42:11.340707775 -0400
@@ -3,7 +3,7 @@
 def g():
     a = 10
     b = 3
-    res = typing.cast(float, a / b)
+    res = typing.cast('float', a / b)
     rounded = round(res, 2)
     d = Decimal(str(res))
     print(f'{res:.2f}', rounded, d)
3.33 3.33 3.3333333333333335
r2-p003 · Forward reference in TYPE_CHECKING vs runtime
1from __future__ import annotations
2import typing
3if typing.TYPE_CHECKING:
4 from typing import List
5 MyType = List[int]
6x = 5
7y = typing.cast(MyType, [x]) # Rule triggers: MyType is not a string
8print(sum(y))
--- orig.py	2026-10-17 16:42:11.448711607 -0400
+++ fixed.py	2026-10-17 16:42:11.865726400 -0400
@@ -4,5 +4,5 @@
     from typing import List
     MyType = List[int]
 x = 5
-y = typing.cast(MyType, [x])  # Rule triggers: MyType is not a string
+y = typing.cast("MyType", [x])  # Rule triggers: MyType is not a string
 print(sum(y))
r2-p004 · Union using | operator in cast
1import typing
2def h(x):
3 return typing.cast(int | str, x)
4print(h(123))
5print(h('abc'))
--- orig.py	2026-10-17 16:42:11.976730337 -0400
+++ fixed.py	2026-10-17 16:42:12.156736715 -0400
@@ -1,5 +1,5 @@
 import typing
 def h(x):
-    return typing.cast(int | str, x)
+    return typing.cast('int | str', x)
 print(h(123))
 print(h('abc'))
123
abc
r2-p005 · F-string nested quotes in cast argument
1import typing
2def i():
3 name = 'Bob'
4 val = typing.cast(list[int], [len(f'{name!r}')])
5 return sum(val)
6print(i())
--- orig.py	2026-10-17 16:42:12.267740647 -0400
+++ fixed.py	2026-10-17 16:42:12.448747059 -0400
@@ -1,6 +1,6 @@
 import typing
 def i():
     name = 'Bob'
-    val = typing.cast(list[int], [len(f'{name!r}')])
+    val = typing.cast('list[int]', [len(f'{name!r}')])
     return sum(val)
 print(i())
5
r2-p006 · Cast with inline comment after argument
1import typing
2j = 7
3k = typing.cast(list[int], [j]) # This is a list of one int
4print(k[0] + 1)
--- orig.py	2026-10-17 16:42:12.549750637 -0400
+++ fixed.py	2026-10-17 16:42:12.732757119 -0400
@@ -1,4 +1,4 @@
 import typing
 j = 7
-k = typing.cast(list[int], [j])  # This is a list of one int
+k = typing.cast("list[int]", [j])  # This is a list of one int
 print(k[0] + 1)
8
r2-p007 · Single-quoted string literal already in cast
1import typing
2x = 42
3y = typing.cast('int', x) # Already quoted: rule should not fire
4print(isinstance(y, int))
True
r2-p008 · Mutable default argument and aliasing
1import typing
2def bad(l=None):
3 if l is None:
4 l = []
5 l.append(typing.cast(int, len(l)))
6 return l
7print(bad())
8print(bad())
9print(bad([]))
--- orig.py	2026-10-17 16:42:12.995766436 -0400
+++ fixed.py	2026-10-17 16:42:13.176772839 -0400
@@ -2,7 +2,7 @@
 def bad(l=None):
     if l is None:
         l = []
-    l.append(typing.cast(int, len(l)))
+    l.append(typing.cast("int", len(l)))
     return l
 print(bad())
 print(bad())
[0]
[0]
[0]