@@ -158,29 +158,58 @@ def _type_head_params(type_head) -> set[str]:
158158 return params
159159
160160 def _unwrap_call_expression (node ):
161- """Return the `call_expression` at or under `node`, unwrapping a
162- `where_expression` (`f(x::T) where {T}` nests the call head one level
163- deeper) ; otherwise None."""
164- if node is None :
165- return None
166- if node .type == "call_expression" :
161+ """Return the `call_expression` at or under `node`, unwrapping any
162+ `where_expression` (`f(x::T) where {T}`) and `typed_expression`
163+ (`f(x)::Float64`) wrappers around the call head ; otherwise None."""
164+ while node is not None and node . type in ( "where_expression" , "typed_expression" ) :
165+ node = node . children [ 0 ] if node . children else None
166+ if node is not None and node .type == "call_expression" :
167167 return node
168- if node .type == "where_expression" :
169- return next ((c for c in node .children if c .type == "call_expression" ), None )
170168 return None
171169
172- def _func_name_from_signature (sig_node ) -> str | None :
173- """Extract function name from a Julia signature node (call_expression > identifier).
170+ def _def_name (call ) -> tuple [str , str ] | None :
171+ """(bare_name, label) for the callee of a method definition.
172+
173+ Handles `foo`, `+`, `Base.show`, `Base.:+`, and functors `(c::Circle)(x)`.
174+ The id uses the bare name so methods of one generic function share a node
175+ (multiple dispatch) and in-file calls resolve to it; the label keeps the
176+ qualifier so `Base.show()` extensions stay recognisable.
177+ """
178+ if call is None or not call .children :
179+ return None
180+ callee = call .children [0 ]
181+ if callee .type in ("identifier" , "operator" ):
182+ name = _read_text (callee , source )
183+ return name , name
184+ if callee .type == "field_expression" and callee .children :
185+ last = callee .children [- 1 ]
186+ if last .type == "quote_expression" : # Base.:+
187+ last = next ((c for c in last .children
188+ if c .type in ("operator" , "identifier" )), None )
189+ if last is not None and last .type in ("identifier" , "operator" ):
190+ return _read_text (last , source ), _read_text (callee , source )
191+ return None
192+ if callee .type == "parenthesized_expression" :
193+ # functor: (c::Circle)(x) or (::Circle)(x) -> call operator on Circle
194+ typed = next ((c for c in callee .children
195+ if c .type in ("typed_expression" , "unary_typed_expression" )), None )
196+ if typed is not None :
197+ ids = [c for c in typed .children if c .type == "identifier" ]
198+ if ids :
199+ tname = _read_text (ids [- 1 ], source )
200+ return tname + "#call" , f"({ tname } )"
201+ return None
202+
203+ def _func_name_from_signature (sig_node ) -> tuple [str , str ] | None :
204+ """(bare_name, label) from a Julia `signature` node, or None.
174205
175- A `where` clause wraps the call head in a `where_expression` , so unwrap
176- that before reading the callee identifier .
206+ `where` clauses and `::RetType` annotations wrap the call head , so
207+ unwrap those before reading the callee.
177208 """
178209 for child in sig_node .children :
179210 call = _unwrap_call_expression (child )
180- if call is not None and call .children :
181- callee = call .children [0 ]
182- if callee .type == "identifier" :
183- return _read_text (callee , source )
211+ if call is not None :
212+ return _def_name (call )
184213 return None
185214
186215 def walk_calls (body_node , func_nid : str ) -> None :
@@ -204,6 +233,18 @@ def walk_calls(body_node, func_nid: str) -> None:
204233 target_nid = _make_id (stem , method_name )
205234 add_edge (func_nid , target_nid , "calls" , body_node .start_point [0 ] + 1 ,
206235 confidence = "EXTRACTED" , context = "call" )
236+ # Broadcast call: foo.(xs)
237+ elif t == "broadcast_call_expression" and body_node .children :
238+ callee = body_node .children [0 ]
239+ if callee .type == "identifier" :
240+ add_edge (func_nid , _make_id (stem , _read_text (callee , source )), "calls" ,
241+ body_node .start_point [0 ] + 1 , confidence = "EXTRACTED" , context = "call" )
242+ # Pipe: x |> foo, xs .|> foo
243+ elif t == "binary_expression" and len (body_node .children ) == 3 :
244+ op , rhs = body_node .children [1 ], body_node .children [2 ]
245+ if _read_text (op , source ) in ("|>" , ".|>" ) and rhs .type == "identifier" :
246+ add_edge (func_nid , _make_id (stem , _read_text (rhs , source )), "calls" ,
247+ body_node .start_point [0 ] + 1 , confidence = "EXTRACTED" , context = "call" )
207248 for child in body_node .children :
208249 walk_calls (child , func_nid )
209250
@@ -278,11 +319,12 @@ def walk(node, scope_nid: str) -> None:
278319 if t == "function_definition" :
279320 sig_node = next ((c for c in node .children if c .type == "signature" ), None )
280321 if sig_node :
281- func_name = _func_name_from_signature (sig_node )
282- if func_name :
322+ named = _func_name_from_signature (sig_node )
323+ if named :
324+ func_name , label = named
283325 func_nid = _make_id (stem , func_name )
284326 line = node .start_point [0 ] + 1
285- add_node (func_nid , f"{ func_name } ()" , line )
327+ add_node (func_nid , f"{ label } ()" , line )
286328 add_edge (scope_nid , func_nid , "defines" , line )
287329 function_bodies .append ((func_nid , node ))
288330 return
@@ -294,7 +336,8 @@ def walk(node, scope_nid: str) -> None:
294336 if t == "macro_definition" :
295337 sig_node = next ((c for c in node .children if c .type == "signature" ), None )
296338 if sig_node :
297- macro_name = _func_name_from_signature (sig_node )
339+ named = _func_name_from_signature (sig_node )
340+ macro_name = named [0 ] if named else None
298341 if macro_name :
299342 macro_nid = _make_id (stem , "@" + macro_name )
300343 line = node .start_point [0 ] + 1
@@ -366,19 +409,17 @@ def _member_ident(n):
366409 # Short function: foo(x) = expr (and the `where` form: g(x::T) where {T} = expr)
367410 if t == "assignment" :
368411 lhs = node .children [0 ] if node .children else None
369- call = _unwrap_call_expression (lhs )
370- if call is not None and call .children :
371- callee = call .children [0 ]
372- if callee .type == "identifier" :
373- func_name = _read_text (callee , source )
374- func_nid = _make_id (stem , func_name )
375- line = node .start_point [0 ] + 1
376- add_node (func_nid , f"{ func_name } ()" , line )
377- add_edge (scope_nid , func_nid , "defines" , line )
378- # Only walk the RHS (index 2 after lhs and operator) to avoid self-loops
379- rhs = node .children [- 1 ] if len (node .children ) >= 3 else None
380- if rhs :
381- function_bodies .append ((func_nid , rhs ))
412+ named = _def_name (_unwrap_call_expression (lhs ))
413+ if named :
414+ func_name , label = named
415+ func_nid = _make_id (stem , func_name )
416+ line = node .start_point [0 ] + 1
417+ add_node (func_nid , f"{ label } ()" , line )
418+ add_edge (scope_nid , func_nid , "defines" , line )
419+ # Only walk the RHS (index 2 after lhs and operator) to avoid self-loops
420+ rhs = node .children [- 1 ] if len (node .children ) >= 3 else None
421+ if rhs :
422+ function_bodies .append ((func_nid , rhs ))
382423 return
383424
384425 # Using / Import
0 commit comments