1212from ..errors import TypedSyntaxError
1313from ..symbols import SymbolVisitor
1414from .effects import NarrowingEffect
15+ from .module_table import ModuleTable
1516from .pyrefly_info import PyreflyTypeInfo
1617from .type_binder import TypeBinder
18+ from .types import Class , Value
1719
1820if 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
63103class 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