@@ -176,77 +176,80 @@ def load_json(cls, json_path: str) -> PyreflyTypeInfo:
176176 data = json .load (f )
177177 return cls (data )
178178
179- def resolve_func (
179+ def _resolve_qname (
180180 self ,
181181 qname : str ,
182182 modules : dict [str , ModuleTable ],
183- type_env : TypeEnvironment ,
184- ) -> Value | None :
185- """Resolve a dotted qname like 'module.path.func_name' to a callable Value.
183+ ) -> tuple [str , Value , list [str ]] | None :
184+ """Resolve a dotted qname into a top-level value and a chain of attributes.
186185
187186 Splits the qname on '.' and tries progressively shorter prefixes
188- as module names, then looks up the function name in the module.
189- This handles both user-defined Functions and built-in callable
190- types like LenFunction.
187+ as module names, then looks up the next name in the chain as a
188+ top-level value in that module. Returns the module name, the top-level
189+ value, and the remaining attributes, e.g. if foo.bar.baz is a module,
190+ _resolve_qname("foo.bar.baz.A.B.f") ->
191+ ("foo.bar.baz", foo.bar.baz.A, ["B", "f"])
191192 """
192193 parts = qname .split ("." )
193-
194194 for i in range (len (parts ) - 1 , 0 , - 1 ):
195195 mod_name = "." .join (parts [:i ])
196196 if mod_name in modules :
197197 mod = modules [mod_name ]
198198 result = mod .get_child (parts [i ], mod_name )
199- if result is None :
200- continue
201- # Walk remaining parts (e.g. Class.method)
202- for part in parts [i + 1 :]:
203- if isinstance (result , Class ):
204- result = result .get_member (part )
205- else :
206- result = None
207- break
208- if result is None :
209- break
210199 if result is not None :
211- return result
200+ return mod_name , result , parts [i + 1 :]
201+ return None
202+
203+ def resolve_func (
204+ self ,
205+ qname : str ,
206+ modules : dict [str , ModuleTable ],
207+ type_env : TypeEnvironment ,
208+ ) -> Value | None :
209+ """Resolve a dotted qname like 'module.path.func_name' to a callable Value.
210+
211+ This handles both user-defined Functions and built-in callable
212+ types like LenFunction.
213+ """
214+ if ret := self ._resolve_qname (qname , modules ):
215+ _ , result , parts = ret
216+ else :
217+ return None
218+ for part in parts :
219+ if isinstance (result , Class ):
220+ result = result .get_member (part )
221+ else :
222+ result = None
223+ if result is None :
224+ break
225+ if result is not None :
226+ return result
212227
213228 return None
214229
215230 def resolve_classname (
216231 self , qname : str , modules : dict [str , ModuleTable ], type_env : TypeEnvironment
217232 ) -> Class | None :
218- """Resolve a dotted qname like 'builtins.int' to a Class.
219-
220- Splits the qname on '.' and tries progressively shorter prefixes
221- as module names, then walks the remainder as nested attributes.
222- """
223- parts = qname .split ("." )
224-
225- # Try progressively shorter prefixes as module names
226- for i in range (len (parts ) - 1 , 0 , - 1 ):
227- mod_name = "." .join (parts [:i ])
228- if mod_name in modules :
229- mod = modules [mod_name ]
230- result = mod .get_child (parts [i ], mod_name )
231- if result is None :
232- continue
233- # Walk any remaining parts (e.g. nested classes)
234- for part in parts [i + 1 :]:
235- if isinstance (result , Class ):
236- # pyre-ignore[16]: `Class` has no attribute `get_child`
237- result = result .get_child (part , mod_name )
238- else :
239- return None
240- if result is None :
241- return None
233+ """Resolve a dotted qname like 'builtins.int' to a Class."""
234+ if ret := self ._resolve_qname (qname , modules ):
235+ mod_name , result , parts = ret
236+ # Walk any remaining parts (e.g. nested classes)
237+ for part in parts :
242238 if isinstance (result , Class ):
243- return result .inexact_type ()
244- elif isinstance (result , Value ):
245- return result .klass
246- return None
239+ # pyre-ignore[16]: `Class` has no attribute `get_child`
240+ result = result .get_child (part , mod_name )
241+ else :
242+ return None
243+ if result is None :
244+ return None
245+ if isinstance (result , Class ):
246+ return result .inexact_type ()
247+ elif isinstance (result , Value ):
248+ return result .klass
249+ return None
247250
248251 # No dot — try builtins
249- if len ( parts ) == 1 :
252+ if "." not in qname :
250253 builtins = modules .get ("builtins" )
251254 if builtins is not None :
252255 result = builtins .get_child (parts [0 ], "builtins" )
0 commit comments