ruff TC006: fix changes program behavior
Hypothesis Nemotron 3 Super predicted to fire fired 1×
shadowed builtins and names redefined later in the module
Program
Output
(no stdout) UnboundLocalError: cannot access local variable 'T' where it is not associated with a value
(no stdout) NameError: name 'x' is not defined
- fired
- 1 before · 0 after
- runs
- each version twice, same file name, no network
Judge Nemotron 3 Ultra advisory real · high
The rule quotes the first argument of typing.cast() to enforce stylistic consistency, but the program uses a forward reference (T is assigned later). Quoting 'T' changes the runtime semantics: before the fix, the UnboundLocalError occurred because T was referenced before assignment; after the fix, the quoted string 'T' is passed, so the cast succeeds but x is undefined, causing a NameError. The documentation's safety condition does not cover forward references, making this a real bug in the fix.
Explanation Nemotron 3 Super unverified
The original uses `cast(T, x)` where `T` refers to the later assignment `T = list`, so `T` resolves to the built-in `list`. After rewriting, `'T'` becomes a string literal passed as the first argument to `cast`, changing its meaning entirely—now interpreted as a type annotation for `str`? Actually `cast('T', x)` treats `'T'` as a forward reference string, but since no such type exists in scope, it fails during type checking or runtime depending on context; however here the immediate difference is that `x` remains undefined in both, but the error shifts because the first argument’s evaluation changes: originally `T` is looked up (failing later due to `x`), while now `'T'` is a constant, so the lookup of `x` happens first and fails earlier with `NameError`. Thus the fix alters runtime behavior by changing what expression is evaluated for the type argument, making it not semantics-preserving.
Reproduce
- file
prog.py(the before program)- minimized program
- r1-p003.py
- evidence sheet
- r1-p003.md
- regression fixture
- r1-p003.py the minimized program with a header stating the observed change, for the tool's test suite
python3 prog.py ruff check --preview --isolated --select TC006 --fix prog.py python3 prog.py
Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra
TC006 fix · ruff TC006 cast undefined variable · ruff TC006 cast string literal conversion · ruff TC006 cast forward reference handling
- related astral-sh/ruff/issues/19596 Both involve TC006 autofix on cast, but existing report concerns EM101 interaction and string literal type, while new finding concerns forward reference redefinition.
- related astral-sh/ruff/issues/14676 Both involve TC006 adding quotes, but existing report is about over-broad quoting in general, while new finding is specifically about quoting a forward reference that gets redefined.
- related astral-sh/ruff/issues/20237 Both involve TC006 and forward references, but existing report is about false positive with from __future__ import annotations, while new finding is about quoting a forward reference that is later redefined to a different value.
Details
- program
- Shadowed builtins and redefined names
- hazard
- shadowed builtins and names redefined later in the module
- oracle
stderr before: UnboundLocalError: cannot access local variable 'T' where it is not associated with a value stderr after: NameError: name 'x' is not defined- run
- 2026-10-17T20-41-40-602Z-mapj-ruff-TC006
generated program (20 lines) and the fix's diff
--- 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)
rule documentation given to the models
# runtime-cast-value (TC006)
Derived from the **flake8-type-checking** linter.
Fix is always available.
## What it does
Checks for unquoted type expressions in `typing.cast()` calls.
## Why is this bad?
This rule helps enforce a consistent style across your codebase.
It's often necessary to quote the first argument passed to `cast()`,
as type expressions can involve forward references, or references
to symbols which are only imported in `typing.TYPE_CHECKING` blocks.
This can lead to a visual inconsistency across different `cast()` calls,
where some type expressions are quoted but others are not. By enabling
this rule, you ensure that all type expressions passed to `cast()` are
quoted, enforcing stylistic consistency across all of your `cast()` calls.
In some cases where `cast()` is used in a hot loop, this rule may also
help avoid overhead from repeatedly evaluating complex type expressions at
runtime.
## Example
```python
from typing import cast
x = cast(dict[str, int], foo)
```
Use instead:
```python
from typing import cast
x = cast("dict[str, int]", foo)
```
## Fix safety
This fix is safe as long as the type expression doesn't span multiple
lines and includes comments on any of the lines apart from the last one.