ruff UP037
labelled unknown
Analysis Nemotron 3 Super
Removes unnecessary quotes from type annotations when they are made redundant by 'from __future__ import annotations' or typing-only contexts.
Fix: Strips quotation marks from type annotations that are deemed unnecessary due to postponed evaluation or typing-only context, transforming e.g., '"Bar"' to Bar while preserving surrounding syntax and whitespace.
10 hazards
- Rewriting a quoted forward reference like '"C"' in a class method to C may fail if C is defined later in the same module and the code relies on runtime evaluation without postponed annotations.
- Removing quotes from an annotation in a default argument like def f(x: "List[]") = [] may cause a NameError if the type is not yet defined at function definition time.
- The fix may incorrectly unquote annotations inside string literals or comments if the linter misidentifies them as type annotations, altering non-code text.
- In lambdas assigned to variables, removing quotes from return annotations like lambda: "int" may break if the annotation is evaluated immediately and the type is not in scope.
- Unquoting annotations in nested scopes (e.g., inside a function) may fail if the referenced type is only defined in an outer scope and not accessible at runtime without postponed evaluation.
- If the file lacks 'from __future__ import annotations' and the preview flag is off, removing quotes may cause runtime NameErrors for forward references that rely on deferred evaluation.
- The fix may remove quotes from annotations in __slots__ or other special attributes where string form is required for correct interpretation by the interpreter.
- In code that uses eval() or exec() on annotations, removing quotes may change the string passed to evaluation, altering behavior if the annotation is inspected dynamically.
- Unquoting may break code that depends on the annotation being a string for introspection (e.g., via inspect.get_annotations()) when postponed evaluation is not active.
- If a quoted annotation contains a trailing or leading space inside the quotes (e.g., " int "), the fix may not preserve internal whitespace, potentially changing the string value if used elsewhere.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | invalid | String literal confusion | Fix may strip quotes from type annotations mistaken for actual string content. |
| r1-p002 | silent prediction off | Lambda immediate evaluation | Unquoting lambda return annotation fails if type not in scope at definition time. |
| r1-p003 | silent prediction off | Nested scope inaccessibility | Unquoting in inner function fails when type is only in outer scope without postponed eval. |
| r1-p004 | silent prediction off | Introspection dependency | Unquoting breaks runtime string inspection when annotations are evaluated eagerly. |
| r1-p005 | silent prediction off | Descriptor side effects | Unquoting annotation on descriptor attribute may trigger early evaluation with side effects. |
| r1-p006 | silent prediction off | Path behavior mismatch | Unquoting annotation in path-related function may alter runtime type expectations. |
| r1-p007 | invalid | Bytes/str encoding | Unquoting annotation changes assumed type for bytes/str handling in I/O. |
| r1-p008 | silent | Custom truthiness interaction | Unquoting annotation on variable with custom __bool__ may affect conditional logic if misparsed. |
| r2-p001 | silent prediction off | Comments/docstring interaction | comments and docstrings attached to the removed or moved code |
| r2-p002 | silent prediction off | Class/instance attribute descriptor side effect | class attributes vs instance attributes, descriptors, properties with side effects |
| r2-p003 | silent prediction off | Missing future import causes NameError | If the file lacks 'from __future__ import annotations' and the preview flag is off, removing quotes may cause runtime NameErrors for forward references that rely on deferred evaluation. |
| r2-p004 | silent prediction off | Eval/exec on annotations alters behavior | In code that uses eval() or exec() on annotations, removing quotes may change the string passed to evaluation, altering behavior if the annotation is inspected dynamically. |
| r2-p005 | silent | __slots__ requires string form | The fix may remove quotes from annotations in __slots__ or other special attributes where string form is required for correct interpretation by the interpreter. |
| r2-p006 | silent prediction off | Late-defined class in same module | Rewriting a quoted forward reference like '"C"' in a class method to C may fail if C is defined later in the same module and the code relies on runtime evaluation without postponed annotations. |
| r2-p007 | silent prediction off | Lambda immediate evaluation out of scope | In lambdas assigned to variables, removing quotes from return annotations like lambda: "int" may break if the annotation is evaluated immediately and the type is not in scope. |
| r2-p008 | silent | Int/float/bool coercion edge case | int/float/bool coercion, Decimal, division and rounding |
Sources
r1-p001 · String literal confusion (generated program does not parse: Traceback (most recent call last):)
1def f():
2 s = '"x:" "int"' # Looks like annotated var but is string
3 eval(s)
4 print('string:', s)\
5def g(x: "int") -> None:
6 print('annotated x:', x)
7g(42)
r1-p002 · Lambda immediate evaluation
1class A: pass
2f = lambda: "A" # Rule may unquote to lambda: A
3try:
4 print('lambda result:', f())
5except Exception as e:
6 print(type(e).__name__, ':', e)
lambda result: A
r1-p003 · Nested scope inaccessibility
1class T: pass
2def outer():
3 def inner() -> "T": # Rule may unquote to -> T
4 return T()
5 return inner()
6print('result:', outer().__class__.__name__)
result: T
r1-p004 · Introspection dependency
1import inspect
2def f(x: "int"): pass # Rule may unquote to x: int
3ann = inspect.get_annotations(f)
4print('annotation type:', type(ann['x']))
5print('annotation value:', ann['x'])
annotation type: <class 'str'> annotation value: int
r1-p005 · Descriptor side effects
1class Desc:
2 def __get__(self, obj, owner):
3 print('Desc accessed')
4 return 42
5class C:
6 x: "Desc" = Desc() # Rule may unquote -> triggers __get__ at class definition
7print('C.x:', C.x)
Desc accessed C.x: 42
r1-p006 · Path behavior mismatch
1import os
2def p(path: "str"): # Rule may unquote to path: str
3 return os.path.exists(path)
4with open('tmp.txt', 'w') as f:
5 f.write('test')
6print('exists:', p('tmp.txt'))
7os.remove('tmp.txt')
exists: True
r1-p007 · Bytes/str encoding (generated program does not parse: Traceback (most recent call last):)
1def read_file(p: "str"): # Rule may unquote to p: str
2 with open(p, 'rb') as f:
3 return f.read()
4data = b'hello
5world'
6with open('test.bin', 'wb') as f:
7 f.write(data)
8result = read_file('test.bin')
9print('bytes:', result)
10import os
11os.remove('test.bin')
r1-p008 · Custom truthiness interaction
1class Truthy:
2 def __bool__(self):
3 return len(self.data) > 0
4 def __init__(self):
5 self.data = [1]
6t = Truthy()
7if t: # Not an annotation, rule should not trigger
8 print('truthy')
9else:
10 print('falsy')
truthy
r2-p001 · Comments/docstring interaction
1"""
2This module tests quote removal next to docstring.
3"""
4class Node:
5 """A node that references itself via forward ref."""
6 parent: "Node" = None
7 def walk(self):
8 p = self.parent
9 print(p.__class__.__name__ if p else 'None')
10Node().walk()
None
r2-p002 · Class/instance attribute descriptor side effect
1class Tracking:
2 def __get__(self, obj, owner):
3 print('Descriptor accessed')
4 return 42
5 def __set__(self, obj, value):
6 print(f'Descriptor set to {value}')
7
8class Container:
9 attr: "Tracking" = Tracking()
10
11print('Accessing attr')
12val = Container.attr
13print(f'Got {val}')
14Container.attr = 99
Accessing attr Descriptor accessed Got 42
r2-p003 · Missing future import causes NameError
1def process(item: "Processor") -> str:
2 return item.name
3
4class Processor:
5 def __init__(self, name):
6 self.name = name
7
8print(process(Processor("test")))
test
r2-p004 · Eval/exec on annotations alters behavior
1import inspect
2
3def func(x: "int") -> "str":
4 return str(x)
5
6ann = inspect.signature(func).parameters['x'].annotation
7print(f'Annotation raw: {repr(ann)}')
8try:
9 result = eval(ann) if isinstance(ann, str) else ann
10 print(f'Eval result: {result}')
11except Exception as e:
12 print(f'Eval error: {type(e).__name__}: {e}')
Annotation raw: 'int' Eval result: <class 'int'>
r2-p005 · __slots__ requires string form
1class SlotClass:
2 __slots__ = ["x", "y"]
3 x: int = 0
4 y: int = 0
5
6obj = SlotClass()
7obj.x = 1
8obj.y = 2
9print(f'x={obj.x}, y={obj.y}')
r2-p006 · Late-defined class in same module
1class Factory:
2 def make(self) -> "Product":
3 return Product()
4
5class Product:
6 def __init__(self):
7 self.value = "created"
8
9f = Factory()
10p = f.make()
11print(p.value)
created
r2-p007 · Lambda immediate evaluation out of scope
1def make_checker():
2 return lambda: "int" # return annotation
3
4checker = make_checker()
5try:
6 ann = checker.__annotations__['return']
7 print(f'Lambda return annotation: {repr(ann)}')
8 if isinstance(ann, str):
9 print('Annotation is string, safe to eval later')
10 else:
11 print('Annotation resolved, assuming type in scope')
12except KeyError:
13 print('No return annotation')
No return annotation
r2-p008 · Int/float/bool coercion edge case
1value: "int" = "42"
2print(f'Value: {value}, type: {type(value)}')
3try:
4 coerced = int(value)
5 print(f'Coerced to int: {coerced}')
6except Exception as e:
7 print(f'Coercion failed: {type(e).__name__}: {e}')
Value: 42, type: <class 'str'> Coerced to int: 42