Skip to content

Commit ef07071

Browse files
martindemellometa-codesync[bot]
authored andcommitted
use pyrefly to lookup node types
Reviewed By: DinoV Differential Revision: D95985104 fbshipit-source-id: 225e3876c1e03fe86bed12eb6114ce60204d9b7f
1 parent d249db7 commit ef07071

1 file changed

Lines changed: 111 additions & 0 deletions

File tree

cinderx/PythonLib/cinderx/compiler/static/type_binder.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from __future__ import annotations
66

77
import ast
8+
import json
89
from ast import (
910
And,
1011
AnnAssign,
@@ -2174,3 +2175,113 @@ def refined_field_index(self, access_path: list[str]) -> int:
21742175

21752176
def _refined_field_name(self, idx: int) -> str:
21762177
return f"{TMP_VAR_PREFIX}.__refined_field__.{idx}"
2178+
2179+
2180+
class PyreflyTypeInfo:
2181+
"""Loads and indexes a pyrefly type trace JSON file.
2182+
2183+
The JSON file contains:
2184+
- type_table: array of type entries (class, literal, callable)
2185+
- locations: array of {loc: {start_line, start_col, end_line, end_col}, type: index}
2186+
2187+
Locations map source positions to type_table indices. Positions use
2188+
1-based lines and 0-based columns (end_col is exclusive), matching
2189+
Python AST conventions.
2190+
"""
2191+
2192+
def __init__(self, json_path: str) -> None:
2193+
with open(json_path) as f:
2194+
data = json.load(f)
2195+
self._type_table: list[dict[str, object]] = data["type_table"]
2196+
self._locations: dict[tuple[int, int, int, int], int] = {}
2197+
for entry in data["locations"]:
2198+
loc = entry["loc"]
2199+
key = (
2200+
loc["start_line"],
2201+
loc["start_col"],
2202+
loc["end_line"],
2203+
loc["end_col"],
2204+
)
2205+
self._locations[key] = entry["type"]
2206+
2207+
def _type_to_str(self, type_index: int) -> str:
2208+
"""Convert a type_table entry to a Python annotation string."""
2209+
entry = self._type_table[type_index]
2210+
kind = entry["kind"]
2211+
if kind == "literal":
2212+
return ""
2213+
elif kind == "class":
2214+
qname = str(entry["qname"])
2215+
args = entry.get("args", [])
2216+
assert isinstance(args, list)
2217+
if not args:
2218+
return qname
2219+
arg_strs = [self._type_to_str(a) for a in args]
2220+
if any(not s for s in arg_strs):
2221+
return qname
2222+
return f"{qname}[{', '.join(arg_strs)}]"
2223+
elif kind == "callable":
2224+
params = entry.get("params", [])
2225+
assert isinstance(params, list)
2226+
ret = entry.get("return_type")
2227+
param_strs = [self._type_to_str(p) for p in params]
2228+
ret_str = self._type_to_str(ret) if isinstance(ret, int) else ""
2229+
if any(not s for s in param_strs) or not ret_str:
2230+
return ""
2231+
return f"Callable[[{', '.join(param_strs)}], {ret_str}]"
2232+
return ""
2233+
2234+
def lookup(self, node: AST) -> str:
2235+
"""Look up the type string for an AST node by its source position."""
2236+
key = (
2237+
node.lineno, # pyre-ignore[16]
2238+
node.col_offset, # pyre-ignore[16]
2239+
node.end_lineno, # pyre-ignore[16]
2240+
node.end_col_offset, # pyre-ignore[16]
2241+
)
2242+
type_index = self._locations.get(key)
2243+
if type_index is None:
2244+
return ""
2245+
return self._type_to_str(type_index)
2246+
2247+
2248+
class PyreflyTypeBinder(TypeBinder):
2249+
"""TypeBinder that uses pyrefly type inference to set types on expression nodes.
2250+
2251+
For each expression node, looks up the pyrefly-inferred type,
2252+
parses it as an annotation, resolves it, and sets it on the node.
2253+
"""
2254+
2255+
def __init__(
2256+
self,
2257+
symbols: SymbolVisitor,
2258+
filename: str,
2259+
compiler: Compiler,
2260+
module_name: str,
2261+
optimize: int,
2262+
type_info: PyreflyTypeInfo,
2263+
enable_patching: bool = False,
2264+
) -> None:
2265+
super().__init__(
2266+
symbols, filename, compiler, module_name, optimize, enable_patching
2267+
)
2268+
self._type_info = type_info
2269+
2270+
def visit(self, node: AST, *args: object) -> NarrowingEffect | None:
2271+
ret = super().visit(node, *args)
2272+
if isinstance(node, ast.expr):
2273+
type_str = self._type_info.lookup(node)
2274+
if type_str:
2275+
annotation_node = ast.parse(type_str, "", "eval").body
2276+
comp_type = self.module.resolve_annotation(
2277+
annotation_node, self.context_qualname
2278+
)
2279+
if comp_type is not None:
2280+
declared_type = comp_type.instance
2281+
self.set_type(node, declared_type)
2282+
if isinstance(node, Name) and isinstance(node.ctx, ast.Store):
2283+
try:
2284+
self.declare_local(node.id, declared_type)
2285+
except TypedSyntaxError:
2286+
pass # already declared, just update the type
2287+
return ret

0 commit comments

Comments
 (0)