Skip to content

Commit 1b25cb0

Browse files
authored
Merge branch 'main' into fix-dependency-gavid-test-versions
2 parents c45e701 + 3f0f7d9 commit 1b25cb0

21 files changed

Lines changed: 192 additions & 178 deletions

rewrite-python/rewrite/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ requires-python = ">=3.10"
2424
dependencies = [
2525
"cbor2>=5.6.5",
2626
"more_itertools>=10.0.0",
27-
"ty-types>=0.0.19.dev20260223102528", # Type inference CLI for Python type attribution
27+
"ty-types>=0.0.19.dev20260223122104", # Type inference CLI for Python type attribution
2828
"parso>=0.7.1,<0.8", # Python 2/3 parser with CST support (0.8+ dropped Python 2.7 grammar)
2929
]
3030

rewrite-python/rewrite/src/rewrite/python/remove_import.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
from rewrite.java import J
2222
from rewrite.java.support_types import JContainer, JRightPadded
23-
from rewrite.java.tree import Empty, FieldAccess, Identifier, Import, Space
23+
from rewrite.java.tree import Empty, FieldAccess, Identifier, Import, MethodDeclaration, Space
2424
from rewrite.markers import Markers
2525
from rewrite.python.tree import CompilationUnit, MultiImport
2626
from rewrite.python.visitor import PythonVisitor
@@ -129,6 +129,11 @@ def visit_multi_import(self, multi: MultiImport, p) -> J:
129129

130130
def visit_identifier(self, ident: Identifier, p) -> J:
131131
if not self.in_import:
132+
# Inside a function scope, identifiers with field_type are
133+
# local variables (shadowing the import), not actual uses.
134+
if self.cursor.first_enclosing(MethodDeclaration) is not None:
135+
if ident.field_type is not None:
136+
return ident
132137
used.add(ident.simple_name)
133138
return ident
134139

rewrite-python/rewrite/src/rewrite/python/type_mapping.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,12 @@ def _descriptor_to_java_type(self, descriptor: Dict[str, Any]) -> Optional[JavaT
446446

447447
elif kind == 'classLiteral':
448448
class_name = descriptor.get('className', '')
449-
class_type = self._create_class_type(class_name)
449+
module_name = descriptor.get('moduleName')
450+
if module_name and module_name != 'builtins':
451+
fqn = f"{module_name}.{class_name}"
452+
else:
453+
fqn = class_name
454+
class_type = self._create_class_type(fqn)
450455

451456
# Infer Kind from supertypes before resolving them
452457
supertypes = descriptor.get('supertypes', [])
@@ -881,6 +886,10 @@ def _get_declaring_type(self, node: ast.Call) -> Optional[JavaType.FullyQualifie
881886
kind = descriptor.get('kind')
882887
if kind == 'module':
883888
return self._create_class_type(descriptor.get('moduleName', ''))
889+
elif kind in ('function', 'boundMethod'):
890+
module_name = descriptor.get('moduleName')
891+
if module_name and module_name != 'builtins':
892+
return self._create_class_type(module_name)
884893

885894
return self._infer_declaring_type_from_ast(node)
886895

rewrite-python/rewrite/src/rewrite/test/rewrite_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def _parse(self, specs: List[SourceSpec]) -> List[Tuple[SourceSpec, SourceFile]]
227227
continue
228228

229229
# Determine source path
230-
source_path = spec.path or Path(f"{uuid4().hex}.{spec.ext}")
230+
source_path = spec.path or Path(f"_{uuid4().hex}.{spec.ext}")
231231

232232
# Parse the source
233233
source = dedent(spec.before)

rewrite-python/rewrite/tests/python/all/tree/assign_test.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import shutil
2-
3-
import pytest
4-
51
from rewrite.java.support_types import JavaType
62
from rewrite.java.tree import Assignment, Identifier, MethodInvocation
73
from rewrite.python.tree import CompilationUnit
@@ -10,11 +6,6 @@
106

117
Parameterized = JavaType.Parameterized
128

13-
requires_ty_cli = pytest.mark.skipif(
14-
shutil.which('ty-types') is None,
15-
reason="ty-types CLI is not installed"
16-
)
17-
189

1910
def test_assign():
2011
# language=python
@@ -112,7 +103,6 @@ def test_assign_op():
112103
RecipeSpec().rewrite_run(python("a @= 1"))
113104

114105

115-
@requires_ty_cli
116106
def test_assign_type_attribution():
117107
"""Verify that type attribution is populated on assignment AST nodes."""
118108
errors = []
@@ -146,7 +136,6 @@ def visit_assignment(self, assignment, p):
146136
assert not errors, "Type attribution errors:\n" + "\n".join(f" - {e}" for e in errors)
147137

148138

149-
@requires_ty_cli
150139
def test_assign_method_call_type_attribution():
151140
"""Verify type attribution on an assignment from a method call like str.split()."""
152141
errors = []

rewrite-python/rewrite/tests/python/all/tree/binary_test.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
import shutil
2-
3-
import pytest
4-
51
from rewrite.java.support_types import JavaType
62
from rewrite.java.tree import Binary
73
from rewrite.python.tree import CompilationUnit
84
from rewrite.python.visitor import PythonVisitor
95
from rewrite.test import RecipeSpec, python
106

11-
requires_ty_cli = pytest.mark.skipif(
12-
shutil.which('ty-types') is None,
13-
reason="ty-types CLI is not installed"
14-
)
15-
167

178
def test_bool_ops():
189
# language=python
@@ -108,7 +99,6 @@ def test_multiline_tuple_comparison():
10899
),)
109100

110101

111-
@requires_ty_cli
112102
def test_arithmetic_type_attribution():
113103
"""Verify that 1 + 2 has type Int."""
114104
errors = []
@@ -138,7 +128,6 @@ def visit_binary(self, binary, p):
138128
assert not errors, "Type attribution errors:\n" + "\n".join(f" - {e}" for e in errors)
139129

140130

141-
@requires_ty_cli
142131
def test_comparison_type_attribution():
143132
"""Verify that 1 < 2 has type Boolean."""
144133
errors = []
@@ -168,7 +157,6 @@ def visit_binary(self, binary, p):
168157
assert not errors, "Type attribution errors:\n" + "\n".join(f" - {e}" for e in errors)
169158

170159

171-
@requires_ty_cli
172160
def test_boolean_op_type_attribution():
173161
"""Verify that True and False has type Boolean."""
174162
errors = []

rewrite-python/rewrite/tests/python/all/tree/class_test.py

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
1-
import shutil
2-
3-
import pytest
1+
from pathlib import Path
42

53
from rewrite.java.support_types import JavaType
6-
from rewrite.java.tree import Assignment, Identifier
4+
from rewrite.java.tree import Assignment, ClassDeclaration, Identifier
75
from rewrite.python.tree import CompilationUnit
86
from rewrite.python.visitor import PythonVisitor
97
from 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

1710
def test_empty():
1811
# language=python
@@ -125,7 +118,6 @@ class C3(Generic[T], metaclass=type, *[str]):
125118
))
126119

127120

128-
@requires_ty_cli
129121
def 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+
178204
def 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

rewrite-python/rewrite/tests/python/all/tree/collection_literal_test.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import shutil
21
from typing import cast
32

4-
import pytest
5-
63
from rewrite.java import MethodDeclaration, Return
74
from rewrite.java.support_types import JavaType
85
from rewrite.java.tree import Assignment
@@ -12,11 +9,6 @@
129

1310
Parameterized = JavaType.Parameterized
1411

15-
requires_ty_cli = pytest.mark.skipif(
16-
shutil.which('ty-types') is None,
17-
reason="ty-types CLI is not installed"
18-
)
19-
2012

2113
def test_empty_tuple():
2214
# language=python
@@ -107,7 +99,6 @@ def test_list_of_tuples_with_double_parens():
10799
"""))
108100

109101

110-
@requires_ty_cli
111102
def test_list_literal_type_attribution():
112103
"""Verify that [1, 2, 3] has type list."""
113104
errors = []
@@ -139,7 +130,6 @@ def visit_assignment(self, assignment, p):
139130
assert not errors, "Type attribution errors:\n" + "\n".join(f" - {e}" for e in errors)
140131

141132

142-
@requires_ty_cli
143133
def test_dict_literal_type_attribution():
144134
"""Verify that {"a": 1} has type dict."""
145135
errors = []

rewrite-python/rewrite/tests/python/all/tree/def_test.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
import shutil
2-
3-
import pytest
4-
51
from rewrite.java.support_types import JavaType
62
from rewrite.java.tree import MethodDeclaration
73
from rewrite.python.tree import CompilationUnit
84
from rewrite.python.visitor import PythonVisitor
95
from rewrite.test import RecipeSpec, python
106

11-
requires_ty_cli = pytest.mark.skipif(
12-
shutil.which('ty-types') is None,
13-
reason="ty-types CLI is not installed"
14-
)
15-
167

178
def test_async_def():
189
# language=python
@@ -24,7 +15,6 @@ async def main():
2415
))
2516

2617

27-
@requires_ty_cli
2818
def test_async_def_type_attribution():
2919
"""Verify that async def main() -> int has a method_type with a return_type.
3020

rewrite-python/rewrite/tests/python/all/tree/field_access_test.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
import shutil
2-
3-
import pytest
4-
51
from rewrite.java.tree import FieldAccess
62
from rewrite.python.tree import CompilationUnit
73
from rewrite.python.visitor import PythonVisitor
84
from rewrite.test import RecipeSpec, python
95

10-
requires_ty_cli = pytest.mark.skipif(
11-
shutil.which('ty-types') is None,
12-
reason="ty-types CLI is not installed"
13-
)
14-
156

167
# noinspection PyUnresolvedReferences
178
def test_attribute():
@@ -25,7 +16,6 @@ def test_nested_attribute():
2516
RecipeSpec().rewrite_run(python("a = foo.bar.baz"))
2617

2718

28-
@requires_ty_cli
2919
def test_field_access_type_attribution():
3020
"""Verify that os.path has a non-None FieldAccess.type."""
3121
errors = []

0 commit comments

Comments
 (0)