ruff TC008
labelled unknown
Analysis Nemotron 3 Super
Flags unnecessary quotes in PEP 613 explicit type aliases and PEP 695 type statements when the quoted content is a valid type expression without forward references.
Fix: Removes the outer double quotes around the type expression while preserving whitespace and inner structure, converting e.g., "int | None" to int | None.
8 hazards
- Rewriting may break code if the quoted string contains a comment, as the fix does not preserve comments inside the quotes.
- If the type expression relies on a name only available via TYPE_CHECKING imports, removing quotes could cause a NameError at runtime.
- In stub files, removing quotes might violate stub-only type contracts where the unquoted form is not available at runtime.
- If the quoted expression includes a forward reference to a class defined later in the same module, the fix assumes it is defined, which may not hold during execution.
- The fix does not account for string concatenation inside quotes (e.g., "int" "|" "None"), leaving it unchanged but potentially misleading users.
- If the quoted string contains escaped quotes (e.g., "\"int\" |"), the fix incorrectly strips outer quotes, corrupting the expression.
- When the type alias is used in a runtime context (e.g., isinstance), removing quotes could alter behavior if the string was intentionally used as a fallback.
- The fix may interfere with tools that inspect source strings directly, expecting the quotes to be present for metadata or code generation.
Findings
| program | oracle | label | judge | status | summary |
|---|---|---|---|---|---|
| r1-p005 | fix-changes-behavior | labelled safe | real · medium | no match | The rule removes quotes from a PEP 613 type alias that uses concatenated string literals ('int' '|' 'None'), rewriting it to the unquoted union int|None. At ru… |
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Mutable Default Arguments & Aliasing After Rewrite | TC008 removes quotes from 'list[int]' used as a function default argument; if the alias points to a mutable object shared across calls, removing quotes doesn't affect runtime behavior—but misleads dev |
| r1-p002 | fix ok | Bytes vs Str Encoding & Newline Handling | Rewriting 'bytes' to bare identifier breaks code that relied on the string being a type annotation for bytes literals; if misread as str, encoding assumptions fail. |
| r1-p003 | fix ok | Forward Reference Assumed Defined Later | Rule removes quotes from 'SomeClass' assuming it's defined, but if SomeClass is defined later in the module, the bare name causes NameError at runtime. |
| r1-p004 | fix ok | pathlib vs os.path Behavior on Relative Paths | Rewriting 'Path' to bare identifier in alias breaks if user mistakenly uses os.path functions on the type; but since it's a type alias, no runtime effect—yet misleading in static analysis. |
| r1-p005 | fix-changes-behavior prediction off | String Concatenation Inside Quotes Left Unchanged | Rule does not rewrite '"int" "|" "None"' (concatenated strings), so alias remains quoted; if developer expects it to be unquoted, they may misuse it. |
| r1-p006 | fix ok | Tool Expecting Quotes for Metadata Inspection | A tool that scans source for quoted type aliases to extract documentation expects quotes; after fix, it fails to detect the alias. |
| r1-p007 | silent prediction off | String Formatting Edge Cases: % , format(), f-strings | If the quoted type alias is used in an f-string or % formatting, removing quotes changes behavior: e.g., f'{ListInt}' becomes f'{list[int]}' which may eval or raise. |
| r1-p008 | silent prediction off | Evaluation Order & Side Effects in Merged Expressions | If the quoted expression has side effects (e.g., function call), removing quotes doesn't change evaluation—but if the alias is reused in multiple contexts, duplication may cause unexpected behavior. |
Sources
r1-p001 · Mutable Default Arguments & Aliasing After Rewrite
1from typing import TypeAlias
2ListInt: TypeAlias = "list[int]"
3def f(x=ListInt()):
4 x.append(1)
5 return x
6print(f())
7print(f())
r1-p002 · Bytes vs Str Encoding & Newline Handling
1from typing import TypeAlias
2MyBytes: TypeAlias = "bytes"
3def g(b: MyBytes) -> str:
4 return b.decode('utf-8')
5try:
6 print(g(b'hello\n'))
7except Exception as e:
8 print(type(e).__name__, e)
--- orig.py 2026-10-17 16:45:18.878566251 -0400 +++ fixed.py 2026-10-17 16:45:19.063571336 -0400 @@ -1,5 +1,5 @@ from typing import TypeAlias -MyBytes: TypeAlias = "bytes" +MyBytes: TypeAlias = bytes def g(b: MyBytes) -> str: return b.decode('utf-8') try:
hello
r1-p003 · Forward Reference Assumed Defined Later
1type OptCls = "SomeClass"
2class SomeClass:
3 pass
4inst: OptCls = SomeClass()
5print(type(inst).__name__)
--- orig.py 2026-10-17 16:45:19.193574906 -0400 +++ fixed.py 2026-10-17 16:45:19.362579547 -0400 @@ -1,4 +1,4 @@ -type OptCls = "SomeClass" +type OptCls = SomeClass class SomeClass: pass inst: OptCls = SomeClass()
SomeClass
r1-p004 · pathlib vs os.path Behavior on Relative Paths
1from typing import TypeAlias
2from pathlib import Path
3P: TypeAlias = "Path"
4p: P = Path('.')
5print(p.exists())
6print(p.is_absolute())
--- orig.py 2026-10-17 16:45:19.463582321 -0400 +++ fixed.py 2026-10-17 16:45:19.675588143 -0400 @@ -1,6 +1,6 @@ from typing import TypeAlias from pathlib import Path -P: TypeAlias = "Path" +P: TypeAlias = Path p: P = Path('.') print(p.exists()) print(p.is_absolute())
True False
r1-p005 · String Concatenation Inside Quotes Left Unchanged
1from typing import TypeAlias
2BadAlias: TypeAlias = "int" "|" "None"
3print(type(BadAlias))
4print(repr(BadAlias))
--- orig.py 2026-10-17 16:45:19.800591576 -0400 +++ fixed.py 2026-10-17 16:45:20.001597096 -0400 @@ -1,4 +1,4 @@ from typing import TypeAlias -BadAlias: TypeAlias = "int" "|" "None" +BadAlias: TypeAlias = int|None print(type(BadAlias)) print(repr(BadAlias))
<class 'str'> 'int|None'
r1-p006 · Tool Expecting Quotes for Metadata Inspection
1from typing import TypeAlias
2DocAlias: TypeAlias = "str | None" # Tool expects this format
3source = open(__file__).read()
4if '"DocAlias:' in source and '="' in source:
5 print('Tool would find it')
6else:
7 print('Tool misses it')
--- orig.py 2026-10-17 16:45:20.115600222 -0400 +++ fixed.py 2026-10-17 16:45:20.294605132 -0400 @@ -1,5 +1,5 @@ from typing import TypeAlias -DocAlias: TypeAlias = "str | None" # Tool expects this format +DocAlias: TypeAlias = str | None # Tool expects this format source = open(__file__).read() if '"DocAlias:' in source and '="' in source: print('Tool would find it')
Tool would find it
r1-p007 · String Formatting Edge Cases: % , format(), f-strings
1from typing import TypeAlias
2ListInt: TypeAlias = "list[int]"
3s = f"{ListInt}"
4print(s)
5try:
6 eval(s)
7except Exception as e:
8 print(type(e).__name__, e)
list[int]
r1-p008 · Evaluation Order & Side Effects in Merged Expressions
1from typing import TypeAlias
2def get_type():
3 print('Side effect')
4 return 'int'
5SideAlias: TypeAlias = f"{get_type()} | None"
6print('Before use')
7x: SideAlias = 5
8print('After use')
Side effect Before use After use