@@ -225,6 +225,10 @@ defmodule ElixirLS.LanguageServer.Providers.CallHierarchy.Locator do
225225 }
226226 end
227227
228+ defp wrap_atom ( { nil , other } ) , do: { nil , other }
229+ defp wrap_atom ( { atom , other } ) when is_atom ( atom ) , do: { { :atom , atom } , other }
230+ defp wrap_atom ( other ) , do: other
231+
228232 defp expand ( { nil , func } , _env , module , _aliases ) when module != nil ,
229233 do: { nil , func }
230234
@@ -294,13 +298,50 @@ defmodule ElixirLS.LanguageServer.Providers.CallHierarchy.Locator do
294298 else
295299 all_calls = metadata . calls |> Map . values ( ) |> List . flatten ( )
296300
301+ mods_funs = metadata . mods_funs_to_positions
302+ metadata_types = metadata . types
303+
297304 filtered_calls =
298305 all_calls
306+ # Filter out calls with nil function (anonymous function calls)
307+ |> Enum . filter ( fn call -> call . func != nil end )
299308 |> Enum . filter ( fn call ->
300- # Check for the specific function, module and arity
301- call . func == function and
302- call . mod == module and
303- ( arity == :any or call . arity == arity )
309+ # Get environment at call position to expand variables and attributes
310+ { call_line , call_column } = call . position
311+ env = Metadata . get_cursor_env ( metadata , { call_line , call_column || 1 } )
312+
313+ binding_env = Binding . from_env ( env , metadata , call . position )
314+
315+ # Determine the module for local calls
316+ mod_or_nil =
317+ case call . kind do
318+ :local_function -> nil
319+ :local_macro -> nil
320+ _ -> call . mod
321+ end
322+
323+ # Expand variables and attributes to get actual module and function
324+ found =
325+ { mod_or_nil , call . func }
326+ |> wrap_atom
327+ |> expand ( binding_env , env . module , env . aliases )
328+ |> Introspection . actual_mod_fun (
329+ env ,
330+ mods_funs ,
331+ metadata_types ,
332+ call . position ,
333+ false
334+ )
335+
336+ # Check if this call matches our target
337+ case found do
338+ { ^ module , ^ function , true , :mod_fun } ->
339+ # Check arity
340+ arity == :any or call . arity == arity
341+
342+ _ ->
343+ false
344+ end
304345 end )
305346
306347 group_calls_by_caller ( filtered_calls , metadata )
@@ -500,25 +541,61 @@ defmodule ElixirLS.LanguageServer.Providers.CallHierarchy.Locator do
500541 ( end_line == nil or call_line <= end_line )
501542 end )
502543
503- # Group by callee
544+ # Expand calls to resolve variables and attributes, then group by callee
504545 calls
505- |> Enum . group_by ( fn call ->
506- # For local calls (mod == nil), use the module from the current context
507- callee_mod = call . mod || module
508- { callee_mod , call . func , call . arity }
546+ # Filter out calls where func is not an atom (e.g., anonymous functions)
547+ # ElixirSense can produce calls where func is a tuple (AST) for anonymous function calls
548+ |> Enum . filter ( fn call -> is_atom ( call . func ) end )
549+ |> Enum . flat_map ( fn call ->
550+ # Get environment at call position to expand variables and attributes
551+ { call_line , call_column } = call . position
552+ env = Metadata . get_cursor_env ( metadata , { call_line , call_column || 1 } )
553+
554+ binding_env = Binding . from_env ( env , metadata , call . position )
555+
556+ # Expand variables and attributes to get actual module
557+ { expanded_mod , expanded_fun } =
558+ { call . mod , call . func }
559+ |> wrap_atom
560+ |> expand ( binding_env , env . module , env . aliases )
561+
562+ # For outgoing calls, include the call if we could expand the module/function
563+ # or if it's already a concrete module (even if not in metadata)
564+ case { expanded_mod , expanded_fun } do
565+ { nil , nil } ->
566+ # Couldn't expand - skip this call
567+ [ ]
568+
569+ { actual_mod , actual_fun } when is_atom ( actual_mod ) and is_atom ( actual_fun ) ->
570+ # Successfully resolved to a module and function
571+ [ { actual_mod , actual_fun , call . arity , call } ]
572+
573+ { nil , actual_fun } when is_atom ( actual_fun ) ->
574+ # Local call without module - use the module from the function parameter
575+ [ { module , actual_fun , call . arity , call } ]
576+
577+ _ ->
578+ [ ]
579+ end
509580 end )
510- |> Enum . map ( fn { { mod , fun , call_arity } , calls } ->
581+ |> Enum . group_by ( fn { mod , fun , call_arity , _call } ->
582+ { mod , fun , call_arity }
583+ end )
584+ |> Enum . map ( fn { { mod , fun , call_arity } , expanded_calls } ->
585+ # Extract original calls from the expanded tuples
586+ original_calls = Enum . map ( expanded_calls , fn { _ , _ , _ , call } -> call end )
587+
511588 % {
512589 to: % {
513590 name: "#{ inspect ( mod ) } .#{ fun } /#{ call_arity } " ,
514591 kind: GenLSP.Enumerations.SymbolKind . function ( ) ,
515592 uri: nil ,
516- range: build_range_from_call ( List . first ( calls ) ) ,
517- selection_range: build_range_from_call ( List . first ( calls ) ) ,
593+ range: build_range_from_call ( List . first ( original_calls ) ) ,
594+ selection_range: build_range_from_call ( List . first ( original_calls ) ) ,
518595 tags: [ ] ,
519596 detail: nil
520597 } ,
521- from_ranges: Enum . map ( calls , & build_range_from_call / 1 )
598+ from_ranges: Enum . map ( original_calls , & build_range_from_call / 1 )
522599 }
523600 end )
524601 else
0 commit comments