1- import shutil
2-
3- import pytest
1+ from pathlib import Path
42
53from rewrite .java .support_types import JavaType
6- from rewrite .java .tree import Assignment , Identifier
4+ from rewrite .java .tree import Assignment , ClassDeclaration , Identifier
75from rewrite .python .tree import CompilationUnit
86from rewrite .python .visitor import PythonVisitor
97from rewrite .test import RecipeSpec , python
108
11- requires_ty_cli = pytest .mark .skipif (
12- shutil .which ('ty-types' ) is None ,
13- reason = "ty-types CLI is not installed"
14- )
15-
169
1710def test_empty ():
1811 # language=python
@@ -125,7 +118,6 @@ class C3(Generic[T], metaclass=type, *[str]):
125118 ))
126119
127120
128- @requires_ty_cli
129121def test_generic_class_type_params ():
130122 """Verify type parameters on a generic class like class Box[T]."""
131123 errors = []
@@ -174,9 +166,43 @@ def __init__(self, value: T) -> None:
174166 assert not errors , "Type attribution errors:\n " + "\n " .join (f" - { e } " for e in errors )
175167
176168
177- @requires_ty_cli
169+ def test_class_literal_module_qualified_fqn ():
170+ """Verify that a class defined in a module gets a module-qualified FQN on its classLiteral type."""
171+ errors = []
172+
173+ def check_types (source_file ):
174+ assert isinstance (source_file , CompilationUnit )
175+
176+ class TypeChecker (PythonVisitor ):
177+ def visit_class_declaration (self , class_decl , p ):
178+ if not isinstance (class_decl , ClassDeclaration ):
179+ return class_decl
180+ cd_type = class_decl .type
181+ if cd_type is None :
182+ errors .append ("ClassDeclaration.type is None for MyClass" )
183+ elif isinstance (cd_type , JavaType .Class ):
184+ fqn = cd_type ._fully_qualified_name
185+ # The FQN should contain a module prefix (not just bare 'MyClass')
186+ if '.' not in fqn :
187+ errors .append (f"ClassDeclaration.type fqn '{ fqn } ' has no module prefix, expected '<module>.MyClass'" )
188+ return class_decl
189+
190+ TypeChecker ().visit (source_file , None )
191+
192+ # language=python
193+ RecipeSpec (type_attribution = True ).rewrite_run (python (
194+ """\
195+ class MyClass:
196+ pass
197+ """ ,
198+ path = Path ("a.py" ),
199+ after_recipe = check_types ,
200+ ))
201+ assert not errors , "Type attribution errors:\n " + "\n " .join (f" - { e } " for e in errors )
202+
203+
178204def test_class_instance_type_attribution ():
179- """Verify that x = Foo() assigns a type with fqn 'Foo'."""
205+ """Verify that x = Foo() assigns a type with fqn ending in 'Foo'."""
180206 errors = []
181207
182208 def check_types (source_file ):
@@ -189,8 +215,10 @@ def visit_assignment(self, assignment, p):
189215 if assignment .type is None :
190216 errors .append ("Assignment.type is None for Foo()" )
191217 elif isinstance (assignment .type , JavaType .Class ):
192- if assignment .type ._fully_qualified_name != 'Foo' :
193- errors .append (f"Assignment.type fqn is '{ assignment .type ._fully_qualified_name } ', expected 'Foo'" )
218+ fqn = assignment .type ._fully_qualified_name
219+ # Accept 'Foo' or '<module>.Foo' (module-qualified with newer ty-types)
220+ if not fqn .endswith ('Foo' ):
221+ errors .append (f"Assignment.type fqn is '{ fqn } ', expected to end with 'Foo'" )
194222 else :
195223 # Accept any non-None type
196224 pass
0 commit comments