@@ -219,10 +219,18 @@ def __init__(self, function_names_to_find: set[str]) -> None:
219219 # For prefix match, store mapping from prefix-root to candidates for O(1) matching
220220 self ._exact_names = function_names_to_find
221221 self ._prefix_roots : dict [str , list [str ]] = {}
222+ # Precompute sets for faster lookup during visit_Attribute()
223+ self ._dot_names : set [str ] = set ()
224+ self ._dot_methods : dict [str , set [str ]] = {}
225+ self ._class_method_to_target : dict [tuple [str , str ], str ] = {}
222226 for name in function_names_to_find :
223227 if "." in name :
224- root = name .split ("." , 1 )[0 ]
225- self ._prefix_roots .setdefault (root , []).append (name )
228+ root , method = name .rsplit ("." , 1 )
229+ self ._dot_names .add (name )
230+ self ._dot_methods .setdefault (method , set ()).add (root )
231+ self ._class_method_to_target [(root , method )] = name
232+ root_prefix = name .split ("." , 1 )[0 ]
233+ self ._prefix_roots .setdefault (root_prefix , []).append (name )
226234
227235 def visit_Import (self , node : ast .Import ) -> None :
228236 """Handle 'import module' statements."""
@@ -333,44 +341,45 @@ def visit_Attribute(self, node: ast.Attribute) -> None:
333341 if self .found_any_target_function :
334342 return
335343
344+ # Check if this is accessing a target function through an imported module
345+
346+ node_value = node .value
347+ node_attr = node .attr
348+
336349 # Check if this is accessing a target function through an imported module
337350 if (
338- isinstance (node . value , ast .Name )
339- and node . value .id in self .imported_modules
340- and node . attr in self .function_names_to_find
351+ isinstance (node_value , ast .Name )
352+ and node_value .id in self .imported_modules
353+ and node_attr in self .function_names_to_find
341354 ):
342355 self .found_any_target_function = True
343- self .found_qualified_name = node . attr
356+ self .found_qualified_name = node_attr
344357 return
345358
346- if isinstance (node .value , ast .Name ) and node .value .id in self .imported_modules :
347- for target_func in self .function_names_to_find :
348- if "." in target_func :
349- class_name , method_name = target_func .rsplit ("." , 1 )
350- if node .attr == method_name :
351- imported_name = node .value .id
352- original_name = self .alias_mapping .get (imported_name , imported_name )
353- if original_name == class_name :
354- self .found_any_target_function = True
355- self .found_qualified_name = target_func
356- return
359+ # Check for methods via imported modules using precomputed _dot_methods and _class_method_to_target
360+ if isinstance (node_value , ast .Name ) and node_value .id in self .imported_modules :
361+ roots_possible = self ._dot_methods .get (node_attr )
362+ if roots_possible :
363+ imported_name = node_value .id
364+ original_name = self .alias_mapping .get (imported_name , imported_name )
365+ if original_name in roots_possible :
366+ self .found_any_target_function = True
367+ self .found_qualified_name = self ._class_method_to_target [(original_name , node_attr )]
368+ return
357369
358370 # Check if this is accessing a method on an instance variable
359- if isinstance (node .value , ast .Name ) and node .value .id in self .instance_mapping :
360- class_name = self .instance_mapping [node .value .id ]
361- for target_func in self .function_names_to_find :
362- if "." in target_func :
363- target_class , method_name = target_func .rsplit ("." , 1 )
364- if node .attr == method_name and class_name == target_class :
365- self .found_any_target_function = True
366- self .found_qualified_name = target_func
367- return
371+ if isinstance (node_value , ast .Name ) and node_value .id in self .instance_mapping :
372+ class_name = self .instance_mapping [node_value .id ]
373+ roots_possible = self ._dot_methods .get (node_attr )
374+ if roots_possible and class_name in roots_possible :
375+ self .found_any_target_function = True
376+ self .found_qualified_name = self ._class_method_to_target [(class_name , node_attr )]
377+ return
368378
369- # Check if this is accessing a target function through a dynamically imported module
370- # Only if we've detected dynamic imports are being used
371- if self .has_dynamic_imports and node .attr in self .function_names_to_find :
379+ # Check for dynamic import match
380+ if self .has_dynamic_imports and node_attr in self .function_names_to_find :
372381 self .found_any_target_function = True
373- self .found_qualified_name = node . attr
382+ self .found_qualified_name = node_attr
374383 return
375384
376385 self .generic_visit (node )
0 commit comments