Treat type alias statements as name assignments - #874
Conversation
`type X = ...` statements were not registered in the scope, so rename and inline refactoring could not resolve the alias name. The scope visitor now records the alias name as an assigned name. Generic aliases get no assignment value, since their value refers to their own type parameters. Fixes python-rope#862
yangfan-yf-yf
left a comment
There was a problem hiding this comment.
I tested 741e993bed633088ccd687985b85f3d549e77775 on Windows with Python 3.12.3. The six added tests pass, and the complete rename/inline test files pass (205 tests, including those six). Ordinary and generic alias renaming also work in my additional checks, including renaming from an import in another module. Generic alias inlining is refused as described.
I found a correctness issue in the newly enabled non-generic alias inlining: recording node.value as an ordinary assigned value loses the alias object's runtime behavior and its lazy evaluation. Two valid inputs become programs that raise exceptions after inlining.
Python documents both the distinct alias object and lazy __value__ evaluation in TypeAliasType.
For example, inlining Alias in:
type Alias = int
value = Alias.__value__produces:
value = int.__value__The original evaluates to int; the result raises AttributeError: type object 'int' has no attribute '__value__'.
There is also a failure even when the alias is used only in an annotation:
type Alias = Later
def f(x: Alias):
pass
class Later:
passInlining removes the alias and changes the annotation to x: Later, making module execution fail with NameError: name 'Later' is not defined. The original module executes successfully because the alias value is lazy.
I ran both examples against the PR and its base, f005ac786da7d556fdf5733e9b9ddae1eaf27242. The base refuses the inline operation and leaves the working code unchanged; this head accepts it and produces the failures above.
Please preserve these semantics or reject the affected inline operations before returning edits, with regression tests for both cases. Keeping the new alias name resolution while refusing alias inlining until these semantics are handled would also be a safe narrower change. This concern is about the non-generic inline behavior added here, not the explicitly deferred type-parameter renaming support.
Description
Rename and inline refactoring failed on
type X = ...statements because the scope visitor never registered the alias name, so rope could not resolve it. The scope visitor now treats a type alias like an assignment and records the alias name. A generic alias liketype A[T] = list[T]can still be renamed, but it gets no assigned value, so inlining it is refused instead of producinglist[T][int]. This does not cover renaming the type parameter itself (old_name_2in the issue), which needs its own scope for the alias and is a bigger change. I added tests for rename and inline that only run on Python 3.12 and newer, and the whole ropetest suite passes on 3.12.Fixes #862
Checklist (delete if not relevant):