diff --git a/apps/els_lsp/src/els_implementation_provider.erl b/apps/els_lsp/src/els_implementation_provider.erl index a3db95cf4..0c667fe2b 100644 --- a/apps/els_lsp/src/els_implementation_provider.erl +++ b/apps/els_lsp/src/els_implementation_provider.erl @@ -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}) -> diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl index 5fb3fb3bf..5f63ffd21 100644 --- a/apps/els_lsp/src/els_parser.erl +++ b/apps/els_lsp/src/els_parser.erl @@ -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 = @@ -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), diff --git a/apps/els_lsp/test/els_parser_SUITE.erl b/apps/els_lsp/test/els_parser_SUITE.erl index 7993c59b1..c7163884a 100644 --- a/apps/els_lsp/test/els_parser_SUITE.erl +++ b/apps/els_lsp/test/els_parser_SUITE.erl @@ -39,7 +39,8 @@ unicode_clause_pattern/1, latin1_source_code/1, record_comment/1, - pragma_noformat/1 + pragma_noformat/1, + implicit_fun/1 ]). %%============================================================================== @@ -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 %%==============================================================================