Skip to content

Commit

Permalink
Add support for parsing implicit fun with ?MODULE (#1509)
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 acea603
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 acea603

Please sign in to comment.