Skip to content

Commit 4a0ac41

Browse files
martindemellometa-codesync[bot]
authored andcommitted
Use the module table to resolve class names from pyrefly
Summary: Simplifies type extraction from pyrefly as a first pass, restricting our lookup to classes without type parameters. Reviewed By: DinoV Differential Revision: D96055967 fbshipit-source-id: c0518b3c492d41ed8240dd9f7b908fcf0ea8bc79
1 parent 7c1be6f commit 4a0ac41

2 files changed

Lines changed: 72 additions & 13 deletions

File tree

cinderx/PythonLib/cinderx/compiler/static/pyrefly_info.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,38 @@ def _type_to_str(self, type_index: int) -> str:
6161
return f"Callable[[{', '.join(param_strs)}], {ret_str}]"
6262
return ""
6363

64-
def lookup(self, node: AST) -> str:
65-
"""Look up the type string for an AST node by its source position."""
64+
def _lookup(self, node: AST) -> int | None:
65+
"""Look up the type_table index for an AST node by its source position."""
6666
key = (
6767
node.lineno, # pyre-ignore[16]
6868
node.col_offset, # pyre-ignore[16]
6969
node.end_lineno, # pyre-ignore[16]
7070
node.end_col_offset, # pyre-ignore[16]
7171
)
72-
type_index = self._locations.get(key)
72+
return self._locations.get(key)
73+
74+
def lookup(self, node: AST) -> str:
75+
"""Look up the type string for an AST node by its source position."""
76+
type_index = self._lookup(node)
7377
if type_index is None:
7478
return ""
7579
return self._type_to_str(type_index)
7680

81+
def lookup_class_qname(self, node: AST) -> str:
82+
"""Look up the qname for an AST node if its type is a simple class.
83+
84+
We treat generic classes as their unparametrised "base" version,
85+
e.g. A[T] -> A
86+
"""
87+
type_index = self._lookup(node)
88+
if type_index is None:
89+
return ""
90+
entry = self._type_table[type_index]
91+
if entry["kind"] == "class":
92+
# Ignore the generic args
93+
return str(entry["qname"])
94+
return ""
95+
7796
@classmethod
7897
def load_json(cls, json_path: str) -> PyreflyTypeInfo:
7998
with open(json_path) as f:

cinderx/PythonLib/cinderx/compiler/static/pyrefly_type_binder.py

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
from ..errors import TypedSyntaxError
1313
from ..symbols import SymbolVisitor
1414
from .effects import NarrowingEffect
15+
from .module_table import ModuleTable
1516
from .pyrefly_info import PyreflyTypeInfo
1617
from .type_binder import TypeBinder
18+
from .types import Class, Value
1719

1820
if TYPE_CHECKING:
1921
from .compiler import Compiler
@@ -59,6 +61,44 @@ def __hash__(self) -> int:
5961
^ self.end_line << 32
6062
)
6163

64+
def _resolve_classname(qname: str, modules: dict[str, ModuleTable]) -> Class | None:
65+
"""Resolve a dotted qname like 'builtins.int' to a Class.
66+
67+
Splits the qname on '.' and tries progressively shorter prefixes
68+
as module names, then walks the remainder as nested attributes.
69+
"""
70+
parts = qname.split(".")
71+
72+
# Try progressively shorter prefixes as module names
73+
for i in range(len(parts) - 1, 0, -1):
74+
mod_name = ".".join(parts[:i])
75+
if mod_name in modules:
76+
mod = modules[mod_name]
77+
result = mod.get_child(parts[i], mod_name)
78+
if result is None:
79+
continue
80+
# Walk any remaining parts (e.g. nested classes)
81+
for part in parts[i + 1 :]:
82+
if isinstance(result, Class):
83+
result = result.get_child(part, mod_name)
84+
else:
85+
return None
86+
if result is None:
87+
return None
88+
if isinstance(result, Class):
89+
return result
90+
return None
91+
92+
# No dot — try builtins
93+
if len(parts) == 1:
94+
builtins = modules.get("builtins")
95+
if builtins is not None:
96+
result = builtins.get_child(parts[0], "builtins")
97+
if isinstance(result, Class):
98+
return result
99+
100+
return None
101+
62102

63103
class PyreflyTypeInfo:
64104
"""Loads and indexes a pyrefly type trace JSON file.
@@ -130,7 +170,7 @@ class PyreflyTypeBinder(TypeBinder):
130170
"""TypeBinder that uses pyrefly type inference to set types on expression nodes.
131171
132172
For each expression node, looks up the pyrefly-inferred type,
133-
parses it as an annotation, resolves it, and sets it on the node.
173+
resolves it via the module table, and sets it on the node.
134174
"""
135175

136176
def __init__(
@@ -151,15 +191,15 @@ def __init__(
151191

152192
def visit(self, node: AST, *args: object) -> NarrowingEffect | None:
153193
ret = super().visit(node, *args)
154-
if isinstance(node, ast.expr):
155-
type_str = self._type_info.lookup(node)
156-
if type_str:
157-
annotation_node = ast.parse(type_str, "", "eval").body
158-
comp_type = self.module.resolve_annotation(
159-
annotation_node, self.context_qualname
160-
)
161-
if comp_type is not None:
162-
declared_type = comp_type.instance
194+
if isinstance(node, ast.expr) and self._type_info is not None:
195+
# For now, we only try to get type information for classes,
196+
# disregarding their type parameters, and doing nothing if we see a
197+
# some other type_info kind like a callable.
198+
classname = self._type_info.lookup_class_qname(node)
199+
if classname:
200+
resolved = _resolve_classname(classname, self.modules)
201+
if resolved is not None:
202+
declared_type = resolved.instance
163203
self.set_type(node, declared_type)
164204
if isinstance(node, Name) and isinstance(node.ctx, ast.Store):
165205
try:

0 commit comments

Comments
 (0)