Skip to content

Commit 5d457b1

Browse files
committed
Python: Handle specialForm type descriptors from ty-types (#6798)
1 parent 0880a2a commit 5d457b1

2 files changed

Lines changed: 58 additions & 0 deletions

File tree

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,12 @@ def _descriptor_to_java_type(self, descriptor: Dict[str, Any]) -> Optional[JavaT
549549
elif kind == 'property':
550550
return _UNKNOWN
551551

552+
elif kind == 'specialForm':
553+
name = descriptor.get('name', '')
554+
if name:
555+
return self._create_class_type(name)
556+
return _UNKNOWN
557+
552558
elif kind == 'typeVar':
553559
name = descriptor.get('name', '')
554560
if not name:

rewrite-python/rewrite/tests/python/test_type_attribution.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,3 +1465,55 @@ def test_typevar_cached_by_type_id(self):
14651465
result1 = mapping._resolve_type(100)
14661466
result2 = mapping._resolve_type(100)
14671467
assert result1 is result2
1468+
1469+
1470+
@requires_ty_types_cli
1471+
class TestImportedNameTypeAttribution:
1472+
"""Tests for type attribution of names imported via 'from X import Y'.
1473+
1474+
When a name is imported (e.g. `from typing import Callable`) and then
1475+
used in a type annotation, the identifier's field_type should resolve
1476+
to a JavaType.Class with the fully qualified name, not JavaType.Unknown.
1477+
"""
1478+
1479+
def test_typing_callable_has_fqn(self):
1480+
"""typing.Callable in a type annotation should resolve to typing.Callable, not Unknown."""
1481+
source = '''from typing import Callable
1482+
handler: Callable[[int], str] = lambda x: str(x)
1483+
'''
1484+
mapping, tree, tmpdir, client = _make_mapping(source)
1485+
try:
1486+
# handler: Callable[...] — the annotation target 'handler'
1487+
ann_node = tree.body[1] # AnnAssign
1488+
# The annotation itself is the Subscript: Callable[[int], str]
1489+
annotation = ann_node.annotation # ast.Subscript
1490+
# annotation.value is the Name node for 'Callable'
1491+
callable_name = annotation.value
1492+
result = mapping.type(callable_name)
1493+
assert result is not None, "Callable name node should have a type"
1494+
assert not isinstance(result, JavaType.Unknown), \
1495+
f"Expected typing.Callable to resolve to a Class, got Unknown"
1496+
assert isinstance(result, (JavaType.Class, JavaType.FullyQualified)), \
1497+
f"Expected JavaType.Class, got {type(result).__qualname__}"
1498+
assert 'typing' in result._fully_qualified_name, \
1499+
f"Expected FQN containing 'typing', got '{result._fully_qualified_name}'"
1500+
finally:
1501+
_cleanup_mapping(mapping, tmpdir, client)
1502+
1503+
def test_typing_callable_qualified_has_fqn(self):
1504+
"""typing.Callable via qualified access should also resolve correctly."""
1505+
source = '''import typing
1506+
handler: typing.Callable[[int], str] = lambda x: str(x)
1507+
'''
1508+
mapping, tree, tmpdir, client = _make_mapping(source)
1509+
try:
1510+
ann_node = tree.body[1] # AnnAssign
1511+
annotation = ann_node.annotation # ast.Subscript
1512+
# annotation.value is the Attribute node: typing.Callable
1513+
attr_node = annotation.value # ast.Attribute
1514+
result = mapping.type(attr_node)
1515+
assert result is not None, "typing.Callable attribute node should have a type"
1516+
assert not isinstance(result, JavaType.Unknown), \
1517+
f"Expected typing.Callable to resolve to a Class, got Unknown"
1518+
finally:
1519+
_cleanup_mapping(mapping, tmpdir, client)

0 commit comments

Comments
 (0)