@@ -151,6 +151,15 @@ def __init__(self, function_names_to_find: set[str]) -> None:
151151 self .has_dynamic_imports : bool = False
152152 self .wildcard_modules : set [str ] = set ()
153153
154+ # Precompute function_names for prefix search
155+ # For prefix match, store mapping from prefix-root to candidates for O(1) matching
156+ self ._exact_names = function_names_to_find
157+ self ._prefix_roots = {}
158+ for name in function_names_to_find :
159+ if "." in name :
160+ root = name .split ("." , 1 )[0 ]
161+ self ._prefix_roots .setdefault (root , []).append (name )
162+
154163 def visit_Import (self , node : ast .Import ) -> None :
155164 """Handle 'import module' statements."""
156165 if self .found_any_target_function :
@@ -181,30 +190,46 @@ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
181190 if self .found_any_target_function :
182191 return
183192
184- if not node .module :
193+ mod = node .module
194+ if not mod :
185195 return
186196
197+ fnames = self ._exact_names
198+ proots = self ._prefix_roots
199+
187200 for alias in node .names :
188- if alias .name == "*" :
189- self .wildcard_modules .add (node .module )
190- else :
191- imported_name = alias .asname if alias .asname else alias .name
192- self .imported_modules .add (imported_name )
193-
194- # Check for dynamic import functions
195- if node .module == "importlib" and alias .name == "import_module" :
196- self .has_dynamic_imports = True
197-
198- # Check if imported name is a target qualified name
199- if alias .name in self .function_names_to_find :
200- self .found_any_target_function = True
201- self .found_qualified_name = alias .name
202- return
203- # Check if module.name forms a target qualified name
204- qualified_name = f"{ node .module } .{ alias .name } "
205- if qualified_name in self .function_names_to_find :
201+ aname = alias .name
202+ if aname == "*" :
203+ self .wildcard_modules .add (mod )
204+ continue
205+
206+ imported_name = alias .asname if alias .asname else aname
207+ self .imported_modules .add (imported_name )
208+
209+ # Fast check for dynamic import
210+ if mod == "importlib" and aname == "import_module" :
211+ self .has_dynamic_imports = True
212+
213+ qname = f"{ mod } .{ aname } "
214+
215+ # Fast exact match check
216+ if aname in fnames :
217+ self .found_any_target_function = True
218+ self .found_qualified_name = aname
219+ return
220+ if qname in fnames :
221+ self .found_any_target_function = True
222+ self .found_qualified_name = qname
223+ return
224+
225+ # Fast prefix match: only for relevant roots
226+ prefix = qname + "."
227+ # Only bother if one of the targets startswith the prefix-root
228+ candidates = proots .get (qname , ())
229+ for target_func in candidates :
230+ if target_func .startswith (prefix ):
206231 self .found_any_target_function = True
207- self .found_qualified_name = qualified_name
232+ self .found_qualified_name = target_func
208233 return
209234
210235 def visit_Attribute (self , node : ast .Attribute ) -> None :
0 commit comments