Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nifs attribute completion #1537

Merged
merged 1 commit into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/els_core/src/els_poi.erl
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
| keyword_expr
| macro
| module
| nifs
| nifs_entry
| parse_transform
| record
| record_def_field
Expand Down
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_code_navigation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ goto_definition(
) when
Kind =:= application;
Kind =:= implicit_fun;
Kind =:= export_entry
Kind =:= export_entry;
Kind =:= nifs_entry
->
%% try to find local function first
%% fall back to bif search if unsuccessful
Expand Down
4 changes: 4 additions & 0 deletions apps/els_lsp/src/els_compiler_diagnostics.erl
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,10 @@ make_code(erl_lint, {bad_dialyzer_option, _Term}) ->
<<"L1316">>;
make_code(erl_lint, {format_error, {_Fmt, _Args}}) ->
<<"L1317">>;
make_code(erl_lint, {undefined_nif, {_F, _A}}) ->
<<"L1318">>;
make_code(erl_link, no_load_nif) ->
<<"L1319">>;
make_code(erl_lint, _Other) ->
<<"L1399">>;
%% stdlib-3.15.2/src/erl_scan.erl
Expand Down
66 changes: 45 additions & 21 deletions apps/els_lsp/src/els_completion_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@ find_completions(
%% Check for "-export(["
[{'[', _}, {'(', _}, {atom, _, export}, {'-', _}] ->
unexported_definitions(Document, function);
%% Check for "-nifs(["
[{'[', _}, {'(', _}, {atom, _, nifs}, {'-', _}] ->
definitions(Document, function, arity_only, false);
%% Check for "-export_type(["
[{'[', _}, {'(', _}, {atom, _, export_type}, {'-', _}] ->
unexported_definitions(Document, type_definition);
Expand Down Expand Up @@ -353,8 +356,18 @@ find_completions(
{ItemFormat, POIKind} = completion_context(Document, Line, Column, Tokens),
case ItemFormat of
arity_only ->
%% Only complete unexported definitions when in export
unexported_definitions(Document, POIKind);
#{text := Text} = Document,
case
is_in(Document, Line, Column, [nifs]) orelse
is_in_heuristic(Text, <<"nifs">>, Line - 1)
of
true ->
definitions(Document, POIKind, ItemFormat, false);
_ ->
%% Only complete unexported definitions when in
%% export
unexported_definitions(Document, POIKind)
end;
_ ->
case complete_record_field(Opts, Tokens) of
[] ->
Expand Down Expand Up @@ -495,6 +508,7 @@ attributes(Document, Line) ->
snippet(attribute_include),
snippet(attribute_include_lib),
snippet(attribute_on_load),
snippet(attribute_nifs),
snippet(attribute_opaque),
snippet(attribute_record),
snippet(attribute_type),
Expand Down Expand Up @@ -579,6 +593,11 @@ snippet(attribute_on_load) ->
<<"-on_load().">>,
<<"on_load(${1:Function}).">>
);
snippet(attribute_nifs) ->
snippet(
<<"-nifs().">>,
<<"nifs([${1:}]).">>
);
snippet(attribute_export_type) ->
snippet(<<"-export_type().">>, <<"export_type([${1:}]).">>);
snippet(attribute_feature) ->
Expand Down Expand Up @@ -787,7 +806,7 @@ definitions(Document, POIKind, ItemFormat, ExportedOnly) ->
{item_format(), els_poi:poi_kind() | any}.
completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
ItemFormat =
case is_in_export(Document, Line, Column) of
case is_in_mfa_list_attr(Document, Line, Column) of
true ->
arity_only;
false ->
Expand All @@ -811,7 +830,7 @@ completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
true ->
type_definition;
false ->
case is_in(Document, Line, Column, [export, function]) of
case is_in(Document, Line, Column, [export, nifs, function]) of
true ->
function;
false ->
Expand All @@ -820,25 +839,30 @@ completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
end,
{ItemFormat, POIKind}.

-spec is_in_export(els_dt_document:item(), line(), column()) -> boolean().
is_in_export(#{text := Text} = Document, Line, Column) ->
%% Sometimes is_in will be confused because -export() failed to be parsed.
-spec is_in_mfa_list_attr(els_dt_document:item(), line(), column()) -> boolean().
is_in_mfa_list_attr(#{text := Text} = Document, Line, Column) ->
%% Sometimes is_in will be confused because e.g. -export() failed to be parsed.
%% In such case we can use a heuristic to determine if we are inside
%% an export.
is_in(Document, Line, Column, [export, export_type]) orelse
is_in_export_heuristic(Text, Line - 1).
is_in(Document, Line, Column, [export, export_type, nifs]) orelse
is_in_mfa_list_attr_heuristic(Text, Line - 1).

-spec is_in_mfa_list_attr_heuristic(binary(), line()) -> boolean().
is_in_mfa_list_attr_heuristic(Text, Line) ->
is_in_heuristic(Text, <<"export">>, Line) orelse
is_in_heuristic(Text, <<"nifs">>, Line).

-spec is_in_export_heuristic(binary(), line()) -> boolean().
is_in_export_heuristic(Text, Line) ->
-spec is_in_heuristic(binary(), binary(), line()) -> boolean().
is_in_heuristic(Text, Attr, Line) ->
Len = byte_size(Attr),
case els_text:line(Text, Line) of
<<"-export", _/binary>> ->
%% In export
<<"-", Attr:Len/binary, _/binary>> ->
%% In Attr
true;
<<" ", _/binary>> when Line > 1 ->
%% Indented line, continue to search previous line
is_in_export_heuristic(Text, Line - 1);
is_in_heuristic(Text, Attr, Line - 1);
_ ->
%% Not in export
false
end.

Expand Down Expand Up @@ -1409,12 +1433,12 @@ is_exported_heuristic_test_() ->
"-define(FOO, foo).\n"
>>,
[
?_assertEqual(false, is_in_export_heuristic(Text, 0)),
?_assertEqual(true, is_in_export_heuristic(Text, 1)),
?_assertEqual(true, is_in_export_heuristic(Text, 2)),
?_assertEqual(true, is_in_export_heuristic(Text, 3)),
?_assertEqual(true, is_in_export_heuristic(Text, 4)),
?_assertEqual(false, is_in_export_heuristic(Text, 5))
?_assertEqual(false, is_in_mfa_list_attr_heuristic(Text, 0)),
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 1)),
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 2)),
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 3)),
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 4)),
?_assertEqual(false, is_in_mfa_list_attr_heuristic(Text, 5))
].

-endif.
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_crossref_diagnostics.erl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ run(Uri) ->
application,
implicit_fun,
import_entry,
export_entry
export_entry,
nifs_entry
]),
[make_diagnostic(POI) || POI <- POIs, not has_definition(POI, Document)]
end.
Expand Down
1 change: 1 addition & 0 deletions apps/els_lsp/src/els_docs.erl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ docs(Uri, #{kind := Kind, id := {F, A}}) when
Kind =:= application;
Kind =:= implicit_fun;
Kind =:= export_entry;
Kind =:= nifs_entry;
Kind =:= spec
->
M = els_uri:module(Uri),
Expand Down
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_document_highlight_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ kind_groups() ->
application,
implicit_fun,
function,
export_entry
export_entry,
nifs_entry
],
%% record
[
Expand Down
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_dt_references.erl
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ kind_to_category(Kind) when
Kind =:= function;
Kind =:= function_clause;
Kind =:= import_entry;
Kind =:= implicit_fun
Kind =:= implicit_fun;
Kind =:= nifs_entry
->
function;
kind_to_category(Kind) when
Expand Down
31 changes: 30 additions & 1 deletion apps/els_lsp/src/els_parser.erl
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ ensure_dot(Tokens) ->
-spec find_attribute_tokens([erlfmt_scan:token()]) -> [els_poi:poi()].
find_attribute_tokens([{'-', Anno}, {atom, _, Name} | [_ | _] = Rest]) when
Name =:= export;
Name =:= export_type
Name =:= export_type;
Name =:= nifs
->
From = erlfmt_scan:get_anno(location, Anno),
To = erlfmt_scan:get_anno(end_location, lists:last(Rest)),
Expand Down Expand Up @@ -451,6 +452,13 @@ attribute(Tree) ->
AttrName =:= export_type
->
find_export_pois(Tree, AttrName, Arg);
{nifs, [Arg]} ->
Nifs = erl_syntax:list_elements(Arg),
NifsEntries = find_nifs_entry_pois(Nifs),
[
poi(erl_syntax:get_pos(Tree), nifs, get_start_location(Tree))
| NifsEntries
];
{import, [ModTree, ImportList]} ->
case is_atom_node(ModTree) of
{true, _} ->
Expand Down Expand Up @@ -660,6 +668,27 @@ find_export_entry_pois(EntryPoiKind, Exports) ->
]
).

-spec find_nifs_entry_pois([tree()]) ->
[els_poi:poi()].
find_nifs_entry_pois(Nifs) ->
lists:flatten(
[
case get_name_arity(FATree) of
{F, A} ->
FTree = erl_syntax:arity_qualifier_body(FATree),
poi(
erl_syntax:get_pos(FATree),
nifs_entry,
{F, A},
#{name_range => els_range:range(erl_syntax:get_pos(FTree))}
);
false ->
[]
end
|| FATree <- Nifs
]
).

-spec find_import_entry_pois(tree(), [tree()]) -> [els_poi:poi()].
find_import_entry_pois(ModTree, Imports) ->
M = erl_syntax:atom_value(ModTree),
Expand Down
1 change: 1 addition & 0 deletions apps/els_lsp/src/els_range.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ in(#{from := FromA, to := ToA}, #{from := FromB, to := ToB}) ->
els_poi:poi_range().
range({{_Line, _Column} = From, {_ToLine, _ToColumn} = To}, Name, _, _Data) when
Name =:= export;
Name =:= nifs;
Name =:= export_type;
Name =:= spec
->
Expand Down
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_references_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ find_references(Uri, #{
Kind =:= implicit_fun;
Kind =:= function;
Kind =:= export_entry;
Kind =:= export_type_entry
Kind =:= export_type_entry;
Kind =:= nifs_entry
->
Key =
case Id of
Expand Down
9 changes: 6 additions & 3 deletions apps/els_lsp/src/els_rename_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ workspace_edits(Uri, [#{kind := Kind} = POI | _], NewName) when
Kind =:= export_entry;
Kind =:= import_entry;
Kind =:= export_type_entry;
Kind =:= type_application
Kind =:= type_application;
Kind =:= nifs_entry
->
case els_code_navigation:goto_definition(Uri, POI) of
{ok, [{DefUri, DefPOI}]} ->
Expand Down Expand Up @@ -205,7 +206,8 @@ editable_range(#{kind := Kind, data := #{name_range := Range}}, function) when
Kind =:= export_type_entry;
Kind =:= import_entry;
Kind =:= type_application;
Kind =:= type_definition
Kind =:= type_definition;
Kind =:= nifs_entry
->
%% application POI of a local call and
%% type_application POI of a built-in type don't have name_range data
Expand Down Expand Up @@ -267,7 +269,8 @@ changes(Uri, #{kind := function, id := {F, A}}, NewName) ->
|| P <- els_dt_document:pois(Doc, [
export_entry,
function_clause,
spec
spec,
nifs_entry
]),
IsMatch(P)
],
Expand Down
6 changes: 6 additions & 0 deletions apps/els_lsp/test/els_completion_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ attributes(Config) ->
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
kind => ?COMPLETION_ITEM_KIND_SNIPPET,
label => <<"-spec">>
},
#{
insertText => <<"nifs([${1:}]).">>,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
kind => ?COMPLETION_ITEM_KIND_SNIPPET,
label => <<"-nifs().">>
}
] ++ docs_attributes(),
#{result := Completions} =
Expand Down
Loading