ruff UP040
labelled unknown
Analysis Nemotron 3 Super
Flags usage of typing.TypeAlias annotation and typing.TypeAliasType assignment for declaring type aliases.
Fix: Replaces 'Name: TypeAlias = expr' with 'type Name = expr' and 'Name = TypeAliasType("Name", expr[, type_params=params])' with 'type Name = expr', stripping the type_params argument and importing Name if needed.
10 hazards
- Rewriting TypeAlias changes the declaration from a variable annotation to a type statement, which cannot be used as a runtime value (e.g., in isinstance() or issubclass()).
- Rewriting TypeAliasType loses explicit variance (covariant/contravariant) from TypeVar definitions, replacing it with inferred variance which may differ.
- The fix cannot resolve TypeVar definitions from other modules, so bounds, constraints, defaults, or kind are lost, producing unconstrained TypeVars in the generated type parameter list.
- If the TypeAliasType's first argument (the name string) does not match the variable name, the resulting type statement may bind to a different name, breaking references.
- Comments attached to the original TypeAlias or TypeAliasType line may be dropped during the rewrite.
- If the aliased expression contains side effects (e.g., function calls), moving it into a type statement changes evaluation timing from runtime to definition time, potentially altering behavior.
- In nested scopes (e.g., inside functions or classes), the rewritten type statement may not be accessible where the original alias was, due to differing scoping rules for type statements.
- If the module defines a __getattr__ or __dir__ that relies on the TypeAlias or TypeAliasType being present as a runtime attribute, the rewrite will remove it, causing AttributeError.
- The fix may produce invalid syntax if the target version is pre-3.12, as 'type' statements are not supported.
- When multiple type aliases are defined in a single line via tuple unpacking, the fix may incorrectly split or fail to parse the construct.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Variance loss in TypeAliasType -> type | Rewriting TypeAliasType loses explicit variance (covariant/contravariant) from TypeVar definitions, replacing it with inferred variance which may differ. |
| r1-p002 | silent prediction off | Side effect timing in alias expression | If the aliased expression contains side effects (e.g., function calls), moving it into a type statement changes evaluation timing from runtime to definition time, potentially altering behavior. |
| r1-p003 | silent prediction off | Mismatched name in TypeAliasType first argument | If the TypeAliasType's first argument (the name string) does not match the variable name, the resulting type statement may bind to a different name, breaking references. |
| r1-p004 | silent prediction off | Class attribute descriptor interaction | class attributes vs instance attributes, descriptors, properties with side effects |
| r1-p005 | invalid | Evaluation order with side effects in compound expression | evaluation order and side effects when expressions are merged, duplicated or deleted |
| r1-p006 | silent prediction off | Shadowed builtin later redefined | shadowed builtins and names redefined later in the module |
| r1-p007 | silent prediction off | String formatting in alias expression | string formatting edge cases: %, format, f-strings, repr vs str, nested quotes |
| r1-p008 | invalid | Pathlib vs os.path behavior | pathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths |
| r2-p001 | silent prediction off | Class attribute vs instance attribute, descriptor interaction | class attributes vs instance attributes, descriptors, properties with side effects |
| r2-p002 | silent prediction off | Target version pre-3.12: 'type' statement invalid syntax | The fix may produce invalid syntax if the target version is pre-3.12, as 'type' statements are not supported. |
| r2-p003 | silent prediction off | Nested scope accessibility: type statement not visible where alias was | In nested scopes (e.g., inside functions or classes), the rewritten type statement may not be accessible where the original alias was, due to differing scoping rules for type statements. |
| r2-p004 | silent prediction off | __getattr__ / __dir__ relies on TypeAlias as runtime attribute | If the module defines a __getattr__ or __dir__ that relies on the TypeAlias or TypeAliasType being present as a runtime attribute, the rewrite will remove it, causing AttributeError. |
| r2-p005 | silent prediction off | TypeAlias cannot be used in isinstance() after rewrite to type statement | Rewriting TypeAlias changes the declaration from a variable annotation to a type statement, which cannot be used as a runtime value (e.g., in isinstance() or issubclass()). |
| r2-p006 | invalid | Keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once | keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once |
| r2-p007 | silent prediction off | Type annotations evaluated at runtime, typing-only imports | type annotations evaluated at runtime, typing-only imports |
| r2-p008 | silent prediction off | Dict ordering, duplicate keys, __hash__/__eq__ overrides, sets | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
Sources
r1-p001 · Variance loss in TypeAliasType -> type
1from typing import TypeVar, TypeAliasType
2T_co = TypeVar('T_co', covariant=True)
3F = TypeAliasType('F', list[T_co])
4def use(f):
5 return isinstance(f([1]), F)
6print(use(lambda x: x))
7# After fix: `type F = list[T_co]` infers invariance, breaking covariance check
r1-p002 · Side effect timing in alias expression
1counter = 0
2def inc():
3 global counter
4 counter += 1
5 return str(counter)
6A = TypeAliasType('A', inc())
7print('First:', counter)
8try:
9 x: A
10 print('After annotation:', counter)
11except:
12 pass
13print('Final:', counter)
14# After fix: `type A = inc()` runs at module load, counter=1 before any use
r1-p003 · Mismatched name in TypeAliasType first argument
1B = TypeAliasType('WrongName', int)
2print(B)
3print(type(B))
4# After fix: `type WrongName = int` creates name 'WrongName', but var 'B' remains unbound to it
5# So B is still the TypeAliasType object, not the alias; referencing WrongName fails
6try:
7 print(WrongName)
8except NameError as e:
9 print('Missing:', e)
r1-p004 · Class attribute descriptor interaction
1class Desc:
2 def __get__(self, obj, owner):
3 return 'desc'
4C = TypeAliasType('C', Desc)
5class A:
6 attr = C
7print(A().attr)
8print(type(A.attr))
9# After fix: `type C = Desc` makes C a type, not Desc instance; attr becomes class var of type Desc
10# But descriptor protocol only works on instances; A.attr is now a type, not descriptor
11# So A().attr raises AttributeError (no __get__ on type)
12try:
13 print(A().attr)
14except AttributeError as e:
15 print('Attr error:', e)
r1-p005 · Evaluation order with side effects in compound expression (generated program does not parse: Traceback (most recent call last):)
1logs = []
2def log(x):
3 logs.append(x)
4 return x
5D = TypeAliasType('D', (log(1), log(2)))
6print('Logs after alias:', logs)
7try:
8 _, y: D
9 print('Logs after use:', logs)
10except:
11 pass
12# After fix: `type D = (log(1), log(2))` runs at definition, logs=[1,2] before any use
13# So second log call doesn't happen again; logs length differs
14print('Final logs:', logs)
r1-p006 · Shadowed builtin later redefined
1list = lambda: []
2E = TypeAliasType('E', list)
3print('E is:', E)
4print('E() is:', E())
5# After fix: `type E = list` uses built-in list, not the lambda
6# Because type statement binds to current scope's list? No: at definition, list is lambda
7# Wait: in CPython, type statement evaluates RHS in current scope at execution time
8# But the alias is created early; however, the fix moves it to top-level type stmt
9# Which runs during module exec, after list=lambda, so still sees lambda?
10# Actually: module runs top-down. list=lambda comes before type E=list, so sees lambda.
11# But original: E = TypeAliasType('E', list) also sees list=lambda at that line.
12# So same? Need rebinding AFTER.
13# Let's rebind after alias.
14list = int # rebind after
15print('After rebind, list is:', list)
16try:
17 print('E still:', E)
18except:
19 pass
20# Original: E holds reference to old list (lambda). Fix: type E = list uses current list at time of exec.
21# Since type stmt runs after list=int rebind? No: exec order:
22# 1. list = lambda
23# 2. E = TypeAliasType(...) -> sees list=lambda
24# 3. list = int
25# After fix:
26# 1. list = lambda
27# 2. type E = list -> sees list=lambda (still)
28# 3. list = int
29# So same. Need alias BEFORE rebind, but use AFTER.
30# Rewrite:
r1-p007 · String formatting in alias expression
1name = 'world'
2F = TypeAliasType('F', f'Hello {name}')
3print('F is:', F)
4# After fix: `type F = f'Hello {name}'` evaluates f-string at definition time
5# If name changes later, alias doesn't update
6name = 'there'
7print('After name change, F still:', F)
8try:
9 # F is a string literal in fix, so no dynamic behavior
10 pass
11except:
12 pass
13print('Name is now:', name)
r1-p008 · Pathlib vs os.path behavior (generated program is not deterministic)
1import os
2os.makedirs('tmp', exist_ok=True)
3with open('tmp/target.txt', 'w') as f:
4 f.write('real')
5os.symlink('tmp/target.txt', 'tmp/link.txt')
6G = TypeAliasType('G', os.path.realpath('tmp/link.txt'))
7print('Resolved path:', G)
8# After fix: `type G = os.path.realpath('tmp/link.txt')` runs at definition
9# If we delete/recreate symlink after, G doesn't update
10os.unlink('tmp/link.txt')
11os.symlink('tmp/other.txt', 'tmp/link.txt') # will fail, need other.txt
12with open('tmp/other.txt', 'w') as f:
13 f.write('other')
14print('After relink, realpath is now:', os.path.realpath('tmp/link.txt'))
15print('But G is still:', G)
16# Cleanup
17import shutil
18shutil.rmtree('tmp', ignore_errors=True)
r2-p001 · Class attribute vs instance attribute, descriptor interaction
1import typing
2
3class Meta(type):
4 def __get__(cls, obj, owner):
5 return super().__get__(obj, owner)
6
7class C(metaclass=Meta):
8 attr: typing.TypeAlias = int # Rule triggers here
9
10print(C.attr) # Class access via metaclass descriptor
11try:
12 c = C()
13 print(c.attr) # Instance access
14 c.attr = 99
15 print(c.attr) # Override on instance
16 del c.attr
17 print(c.attr) # Back to class via descriptor
18except Exception as e:
19 print(f'{type(e).__name__}: {e}')
<class 'int'> <class 'int'> 99 <class 'int'>
r2-p002 · Target version pre-3.12: 'type' statement invalid syntax
1import sys
2if sys.version_info < (3, 12):
3 print('SKIP: Target version < 3.12, but we still write the code to be fixed')
4 # Simulate what the fix would produce: invalid syntax in <3.12
5 # We cannot actually run invalid syntax, so we show the intent
6 # The rule would rewrite this to `type T = int` which is invalid
7 T: typing.TypeAlias = int # This line is what the rule sees
8 try:
9 # This would fail after fix in <3.12
10 reveal_type(T) # type: ignore
11 except Exception as e:
12 print(f'{type(e).__name__}: {e}')
13else:
14 T: typing.TypeAlias = int
15 print(f'T is {T} and type(T) is {type(T)}')
T is <class 'int'> and type(T) is <class 'type'>
r2-p003 · Nested scope accessibility: type statement not visible where alias was
1import typing
2
3def outer():
4 inner_T: typing.TypeAlias = list[str] # Rule triggers here
5 def inner():
6 return inner_T # After fix: `type inner_T = list[str]` - but is it visible?
7 return inner()
8
9try:
10 result = outer()
11 print(f'Outer returned: {result}')
12 print(f'Isinstance check: {isinstance(["a"], result)}')
13except Exception as e:
14 print(f'{type(e).__name__}: {e}')
Outer returned: list[str] TypeError: isinstance() argument 2 cannot be a parameterized generic
r2-p004 · __getattr__ / __dir__ relies on TypeAlias as runtime attribute
1import typing
2
3T: typing.TypeAlias = int # Rule triggers here
4
5def __getattr__(name):
6 if name == 'T':
7 return 42 # Only called if T is not found normally
8 raise AttributeError(name)
9
10def __dir__():
11 return ['T'] + [x for x in dir() if x != 'T']
12
13print('Before any access:', end=' ')
14try:
15 print(T)
16except AttributeError as e:
17 print(f'AttributeError: {e}')
18
19print('After defining T via getattr:', end=' ')
20try:
21 print(T)
22except AttributeError as e:
23 print(f'AttributeError: {e}')
Before any access: <class 'int'> After defining T via getattr: <class 'int'>
r2-p005 · TypeAlias cannot be used in isinstance() after rewrite to type statement
1import typing
2
3T: typing.TypeAlias = int # Rule triggers here
4
5x = 42
6print(f'isinstance(x, T) before any fix: {isinstance(x, T)}')
7print(f'type(T): {type(T)}')
8print(f'T is: {T}')
9
10# After fix: `type T = int` -> T becomes a type, but cannot be used in isinstance at runtime?
11# Actually, in 3.12+, `type T = int` makes T a type and usable in isinstance.
12# But the hazard is: the rule removes the *variable*, so if code relied on T being a variable...
13# Let's test if T is callable or has id
14print(f'id(T): {id(T)}')
15try:
16 T() # Try to call it as if it were a constructor from a variable
17 print('T() succeeded')
18except Exception as e:
19 print(f'T() failed: {type(e).__name__}: {e}')
isinstance(x, T) before any fix: True type(T): <class 'type'> T is: <class 'int'> id(T): 11118080 T() succeeded
r2-p006 · Keyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once (generated program does not parse: Traceback (most recent call last):)
1import typing
2
3def factory(a, /, b, *, c, d=4, *args, **kwargs):
4 return (a, b, c, d, args, kwargs)
5
6Alias: typing.TypeAlias = factory # Rule triggers here
7
8# Store original defaults
9import inspect
10orig = inspect.signature(factory)
11print(f'Original defaults: {orig.parameters["d"].default}')
12
13# Call via alias
14try:
15 result = Alias(1, 2, c=3, d=99, 4, 5, extra=6)
16 print(f'Alias call result: {result}')
17except Exception as e:
18 print(f'{type(e).__name__}: {e}')
19
20# Now simulate what happens if the fix breaks something
21# But the alias is just a function reference - should be fine
22# Hazard: if the rewrite somehow broke the binding
23print(f'Alias is still callable: {callable(Alias)}')
24print(f'Alias.__name__: {Alias.__name__}')
r2-p007 · Type annotations evaluated at runtime, typing-only imports
1import typing
2
3# Use a typing-only import that would be removed if not used
4from typing import List # This is used in the alias below
5
6Mylist: typing.TypeAlias = List[int] # Rule triggers here
7
8# Force evaluation of the annotation at runtime
9print(f'Mylist: {Mylist}')
10print(f'type(Mylist): {type(Mylist)}')
11
12# Now, if the rewrite happens, and we are in a context where typing is not imported...
13# But we imported it above. So make it conditional
14# Instead, test if the alias retains the ability to resolve List
15try:
16 # This should work because Mylist is a variable holding the resolved type
17 x: Mylist = [1, 2]
18 print(f'Variable annotation with Mylist: {x}')
19except Exception as e:
20 print(f'{type(e).__name__}: {e}')
21
22# After fix: `type Mylist = List[int]` - in 3.12+ this is fine, but if someone
23# deleted the import, it would break. But we didn't.
24# So instead, show that the alias is usable as a type hint
25print('Successfully used Mylist in type annotation')
Mylist: typing.List[int] type(Mylist): <class 'typing._GenericAlias'> Variable annotation with Mylist: [1, 2] Successfully used Mylist in type annotation
r2-p008 · Dict ordering, duplicate keys, __hash__/__eq__ overrides, sets
1import typing
2
3class Weird:
4 def __init__(self, val):
5 self.val = val
6 def __hash__(self):
7 return hash(self.val)
8 def __eq__(self, other):
9 return isinstance(other, Weird) and self.val == other.val
10 def __repr__(self):
11 return f'Weird({self.val})'
12
13# Dict with duplicate keys in construction (later wins)
14# But we can't have duplicate keys in dict literal - so build via pairs
15pairs = [(Weird(1), 'a'), (Weird(1), 'b'), (Weird(2), 'c')] # Same hash, equal
16D: typing.TypeAlias = dict(pairs) # Rule triggers here
17
18print(f'D (alias): {D}')
19print(f'len(D): {len(D)}') # Should be 2: {Weird(1): 'b', Weird(2): 'c'}
20for k, v in D.items():
21 print(f' {k!r}: {v!r}')
22
23# Test if the dict is mutable and if alias reflects changes
24# But alias is the dict object itself
25D[Weird(3)] = 'd'
26print(f'After adding: {D}')
27print(f'len now: {len(D)}')
28
29# Create new dict via alias to see if it's fresh
30try:
31 new = D() # This fails - dict is not callable
32 print('D() succeeded')
33except Exception as e:
34 print(f'D() failed: {type(e).__name__}: {e}')
35
36# But we can use it as a type
37try:
38 y: D = {Weird(4): 'e'}
39 print(f'Successfully used D as type annotation: {y}')
40except Exception as e:
41 print(f'{type(e).__name__}: {e}')
D (alias): {Weird(1): 'b', Weird(2): 'c'}
len(D): 2
Weird(1): 'b'
Weird(2): 'c'
After adding: {Weird(1): 'b', Weird(2): 'c', Weird(3): 'd'}
len now: 3
D() failed: TypeError: 'dict' object is not callable
Successfully used D as type annotation: {Weird(4): 'e'}