Skip to content

Commit

Permalink
Add support for parsing implicit fun with ?MODULE
Browse files Browse the repository at this point in the history
Also add support for parsing variables in implicit funs.
  • Loading branch information
plux committed Apr 30, 2024
1 parent 908d9e8 commit 8fc92cd
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 4 deletions.
7 changes: 5 additions & 2 deletions apps/els_lsp/src/els_implementation_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ find_implementation(Document, Line, Character) ->
implementation(
Document,
#{
kind := application,
kind := Kind,
id := {_M, F, A},
data := #{mod_is_variable := true}
} = POI
) ->
) when
Kind =:= application;
Kind =:= implicit_fun
->
%% Try to handle Mod:function() by assuming it is a behaviour callback.
implementation(Document, POI#{kind => callback, id => {F, A}});
implementation(Document, #{kind := application, id := MFA}) ->
Expand Down
67 changes: 66 additions & 1 deletion apps/els_lsp/src/els_parser.erl
Original file line number Diff line number Diff line change
Expand Up @@ -831,9 +831,36 @@ implicit_fun(Tree) ->
throw:syntax_error ->
undefined
end,

case FunSpec of
undefined ->
[];
NameTree = erl_syntax:implicit_fun_name(Tree),
case try_analyze_implicit_fun(Tree) of
{{ModType, Mod}, {FunType, Function}, Arity} ->
ModTree = erl_syntax:module_qualifier_argument(NameTree),
FunTree = erl_syntax:arity_qualifier_body(
erl_syntax:module_qualifier_body(NameTree)
),
Data = #{
name_range => els_range:range(erl_syntax:get_pos(FunTree)),
mod_range => els_range:range(erl_syntax:get_pos(ModTree)),
mod_is_variable => ModType =:= variable,
fun_is_variable => FunType =:= variable
},
[poi(erl_syntax:get_pos(Tree), implicit_fun, {Mod, Function, Arity}, Data)];
{Function, Arity} ->
ModTree = erl_syntax:module_qualifier_argument(NameTree),
FunTree = erl_syntax:arity_qualifier_body(
erl_syntax:module_qualifier_body(NameTree)
),
Data = #{
name_range => els_range:range(erl_syntax:get_pos(FunTree)),
mod_range => els_range:range(erl_syntax:get_pos(ModTree))
},
[poi(erl_syntax:get_pos(Tree), implicit_fun, {Function, Arity}, Data)];
_ ->
[]
end;
_ ->
NameTree = erl_syntax:implicit_fun_name(Tree),
Data =
Expand All @@ -854,6 +881,44 @@ implicit_fun(Tree) ->
[poi(erl_syntax:get_pos(Tree), implicit_fun, FunSpec, Data)]
end.

-spec try_analyze_implicit_fun(tree()) ->
{{atom(), atom()}, {atom(), atom()}, arity()}
| {atom(), arity()}
| undefined.
try_analyze_implicit_fun(Tree) ->
FunName = erl_syntax:implicit_fun_name(Tree),
ModQBody = erl_syntax:module_qualifier_body(FunName),
ModQArg = erl_syntax:module_qualifier_argument(FunName),
case erl_syntax:type(ModQBody) of
arity_qualifier ->
AqBody = erl_syntax:arity_qualifier_body(ModQBody),
AqArg = erl_syntax:arity_qualifier_argument(ModQBody),
case {erl_syntax:type(ModQArg), erl_syntax:type(AqBody), erl_syntax:type(AqArg)} of
{macro, atom, integer} ->
M = erl_syntax:variable_name(erl_syntax:macro_name(ModQArg)),
F = erl_syntax:atom_value(AqBody),
A = erl_syntax:integer_value(AqArg),
case M of
'MODULE' ->
{F, A};
_ ->
undefined
end;
{ModType, FunType, integer} when
ModType =:= variable orelse ModType =:= atom,
FunType =:= variable orelse FunType =:= atom
->
M = node_name(ModQArg),
F = node_name(AqBody),
A = erl_syntax:integer_value(AqArg),
{{ModType, M}, {FunType, F}, A};
_Types ->
undefined
end;
_Type ->
undefined
end.

-spec macro(tree()) -> [els_poi:poi()].
macro(Tree) ->
Anno = macro_location(Tree),
Expand Down
26 changes: 25 additions & 1 deletion apps/els_lsp/test/els_parser_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
unicode_clause_pattern/1,
latin1_source_code/1,
record_comment/1,
pragma_noformat/1
pragma_noformat/1,
implicit_fun/1
]).

%%==============================================================================
Expand Down Expand Up @@ -539,6 +540,29 @@ pragma_noformat(_Config) ->
?assertMatch({ok, _}, els_parser:parse(Text)),
?assertMatch({ok, _}, els_parser:parse_text(Text)).

implicit_fun(_Config) ->
?assertMatch(
[#{id := {foo, 0}}],
parse_find_pois(<<"fun foo/0">>, implicit_fun)
),
?assertMatch(
[#{id := {foo, foo, 0}}],
parse_find_pois(<<"fun foo:foo/0">>, implicit_fun)
),
?assertMatch(
[#{id := {'Var', foo, 0}, data := #{mod_is_variable := true}}],
parse_find_pois(<<"fun Var:foo/0">>, implicit_fun)
),
?assertMatch(
[#{id := {foo, 'Var', 0}, data := #{fun_is_variable := true}}],
parse_find_pois(<<"fun foo:Var/0">>, implicit_fun)
),
?assertMatch(
[#{id := {foo, 0}}],
parse_find_pois(<<"fun ?MODULE:foo/0">>, implicit_fun)
),
ok.

%%==============================================================================
%% Helper functions
%%==============================================================================
Expand Down

0 comments on commit 8fc92cd

Please sign in to comment.