Skip to content

Commit 79eab37

Browse files
authored
nifs attribute completion (#1537)
1 parent de391a6 commit 79eab37

13 files changed

+105
-30
lines changed

apps/els_core/src/els_poi.erl

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
| keyword_expr
4747
| macro
4848
| module
49+
| nifs
50+
| nifs_entry
4951
| parse_transform
5052
| record
5153
| record_def_field

apps/els_lsp/src/els_code_navigation.erl

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ goto_definition(
6060
) when
6161
Kind =:= application;
6262
Kind =:= implicit_fun;
63-
Kind =:= export_entry
63+
Kind =:= export_entry;
64+
Kind =:= nifs_entry
6465
->
6566
%% try to find local function first
6667
%% fall back to bif search if unsuccessful

apps/els_lsp/src/els_compiler_diagnostics.erl

+4
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,10 @@ make_code(erl_lint, {bad_dialyzer_option, _Term}) ->
536536
<<"L1316">>;
537537
make_code(erl_lint, {format_error, {_Fmt, _Args}}) ->
538538
<<"L1317">>;
539+
make_code(erl_lint, {undefined_nif, {_F, _A}}) ->
540+
<<"L1318">>;
541+
make_code(erl_link, no_load_nif) ->
542+
<<"L1319">>;
539543
make_code(erl_lint, _Other) ->
540544
<<"L1399">>;
541545
%% stdlib-3.15.2/src/erl_scan.erl

apps/els_lsp/src/els_completion_provider.erl

+45-21
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ find_completions(
308308
%% Check for "-export(["
309309
[{'[', _}, {'(', _}, {atom, _, export}, {'-', _}] ->
310310
unexported_definitions(Document, function);
311+
%% Check for "-nifs(["
312+
[{'[', _}, {'(', _}, {atom, _, nifs}, {'-', _}] ->
313+
definitions(Document, function, arity_only, false);
311314
%% Check for "-export_type(["
312315
[{'[', _}, {'(', _}, {atom, _, export_type}, {'-', _}] ->
313316
unexported_definitions(Document, type_definition);
@@ -353,8 +356,18 @@ find_completions(
353356
{ItemFormat, POIKind} = completion_context(Document, Line, Column, Tokens),
354357
case ItemFormat of
355358
arity_only ->
356-
%% Only complete unexported definitions when in export
357-
unexported_definitions(Document, POIKind);
359+
#{text := Text} = Document,
360+
case
361+
is_in(Document, Line, Column, [nifs]) orelse
362+
is_in_heuristic(Text, <<"nifs">>, Line - 1)
363+
of
364+
true ->
365+
definitions(Document, POIKind, ItemFormat, false);
366+
_ ->
367+
%% Only complete unexported definitions when in
368+
%% export
369+
unexported_definitions(Document, POIKind)
370+
end;
358371
_ ->
359372
case complete_record_field(Opts, Tokens) of
360373
[] ->
@@ -476,6 +489,7 @@ attributes(Document, Line) ->
476489
snippet(attribute_include),
477490
snippet(attribute_include_lib),
478491
snippet(attribute_on_load),
492+
snippet(attribute_nifs),
479493
snippet(attribute_opaque),
480494
snippet(attribute_record),
481495
snippet(attribute_type),
@@ -562,6 +576,11 @@ snippet(attribute_on_load) ->
562576
<<"-on_load().">>,
563577
<<"on_load(${1:Function}).">>
564578
);
579+
snippet(attribute_nifs) ->
580+
snippet(
581+
<<"-nifs().">>,
582+
<<"nifs([${1:}]).">>
583+
);
565584
snippet(attribute_export_type) ->
566585
snippet(<<"-export_type().">>, <<"export_type([${1:}]).">>);
567586
snippet(attribute_feature) ->
@@ -770,7 +789,7 @@ definitions(Document, POIKind, ItemFormat, ExportedOnly) ->
770789
{item_format(), els_poi:poi_kind() | any}.
771790
completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
772791
ItemFormat =
773-
case is_in_export(Document, Line, Column) of
792+
case is_in_mfa_list_attr(Document, Line, Column) of
774793
true ->
775794
arity_only;
776795
false ->
@@ -794,7 +813,7 @@ completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
794813
true ->
795814
type_definition;
796815
false ->
797-
case is_in(Document, Line, Column, [export, function]) of
816+
case is_in(Document, Line, Column, [export, nifs, function]) of
798817
true ->
799818
function;
800819
false ->
@@ -803,25 +822,30 @@ completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
803822
end,
804823
{ItemFormat, POIKind}.
805824

806-
-spec is_in_export(els_dt_document:item(), line(), column()) -> boolean().
807-
is_in_export(#{text := Text} = Document, Line, Column) ->
808-
%% Sometimes is_in will be confused because -export() failed to be parsed.
825+
-spec is_in_mfa_list_attr(els_dt_document:item(), line(), column()) -> boolean().
826+
is_in_mfa_list_attr(#{text := Text} = Document, Line, Column) ->
827+
%% Sometimes is_in will be confused because e.g. -export() failed to be parsed.
809828
%% In such case we can use a heuristic to determine if we are inside
810829
%% an export.
811-
is_in(Document, Line, Column, [export, export_type]) orelse
812-
is_in_export_heuristic(Text, Line - 1).
830+
is_in(Document, Line, Column, [export, export_type, nifs]) orelse
831+
is_in_mfa_list_attr_heuristic(Text, Line - 1).
832+
833+
-spec is_in_mfa_list_attr_heuristic(binary(), line()) -> boolean().
834+
is_in_mfa_list_attr_heuristic(Text, Line) ->
835+
is_in_heuristic(Text, <<"export">>, Line) orelse
836+
is_in_heuristic(Text, <<"nifs">>, Line).
813837

814-
-spec is_in_export_heuristic(binary(), line()) -> boolean().
815-
is_in_export_heuristic(Text, Line) ->
838+
-spec is_in_heuristic(binary(), binary(), line()) -> boolean().
839+
is_in_heuristic(Text, Attr, Line) ->
840+
Len = byte_size(Attr),
816841
case els_text:line(Text, Line) of
817-
<<"-export", _/binary>> ->
818-
%% In export
842+
<<"-", Attr:Len/binary, _/binary>> ->
843+
%% In Attr
819844
true;
820845
<<" ", _/binary>> when Line > 1 ->
821846
%% Indented line, continue to search previous line
822-
is_in_export_heuristic(Text, Line - 1);
847+
is_in_heuristic(Text, Attr, Line - 1);
823848
_ ->
824-
%% Not in export
825849
false
826850
end.
827851

@@ -1392,12 +1416,12 @@ is_exported_heuristic_test_() ->
13921416
"-define(FOO, foo).\n"
13931417
>>,
13941418
[
1395-
?_assertEqual(false, is_in_export_heuristic(Text, 0)),
1396-
?_assertEqual(true, is_in_export_heuristic(Text, 1)),
1397-
?_assertEqual(true, is_in_export_heuristic(Text, 2)),
1398-
?_assertEqual(true, is_in_export_heuristic(Text, 3)),
1399-
?_assertEqual(true, is_in_export_heuristic(Text, 4)),
1400-
?_assertEqual(false, is_in_export_heuristic(Text, 5))
1419+
?_assertEqual(false, is_in_mfa_list_attr_heuristic(Text, 0)),
1420+
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 1)),
1421+
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 2)),
1422+
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 3)),
1423+
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 4)),
1424+
?_assertEqual(false, is_in_mfa_list_attr_heuristic(Text, 5))
14011425
].
14021426

14031427
-endif.

apps/els_lsp/src/els_crossref_diagnostics.erl

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ run(Uri) ->
4040
application,
4141
implicit_fun,
4242
import_entry,
43-
export_entry
43+
export_entry,
44+
nifs_entry
4445
]),
4546
[make_diagnostic(POI) || POI <- POIs, not has_definition(POI, Document)]
4647
end.

apps/els_lsp/src/els_docs.erl

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ docs(Uri, #{kind := Kind, id := {F, A}}) when
5454
Kind =:= application;
5555
Kind =:= implicit_fun;
5656
Kind =:= export_entry;
57+
Kind =:= nifs_entry;
5758
Kind =:= spec
5859
->
5960
M = els_uri:module(Uri),

apps/els_lsp/src/els_document_highlight_provider.erl

+2-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ kind_groups() ->
122122
application,
123123
implicit_fun,
124124
function,
125-
export_entry
125+
export_entry,
126+
nifs_entry
126127
],
127128
%% record
128129
[

apps/els_lsp/src/els_dt_references.erl

+2-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ kind_to_category(Kind) when
161161
Kind =:= function;
162162
Kind =:= function_clause;
163163
Kind =:= import_entry;
164-
Kind =:= implicit_fun
164+
Kind =:= implicit_fun;
165+
Kind =:= nifs_entry
165166
->
166167
function;
167168
kind_to_category(Kind) when

apps/els_lsp/src/els_parser.erl

+30-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ ensure_dot(Tokens) ->
217217
-spec find_attribute_tokens([erlfmt_scan:token()]) -> [els_poi:poi()].
218218
find_attribute_tokens([{'-', Anno}, {atom, _, Name} | [_ | _] = Rest]) when
219219
Name =:= export;
220-
Name =:= export_type
220+
Name =:= export_type;
221+
Name =:= nifs
221222
->
222223
From = erlfmt_scan:get_anno(location, Anno),
223224
To = erlfmt_scan:get_anno(end_location, lists:last(Rest)),
@@ -451,6 +452,13 @@ attribute(Tree) ->
451452
AttrName =:= export_type
452453
->
453454
find_export_pois(Tree, AttrName, Arg);
455+
{nifs, [Arg]} ->
456+
Nifs = erl_syntax:list_elements(Arg),
457+
NifsEntries = find_nifs_entry_pois(Nifs),
458+
[
459+
poi(erl_syntax:get_pos(Tree), nifs, get_start_location(Tree))
460+
| NifsEntries
461+
];
454462
{import, [ModTree, ImportList]} ->
455463
case is_atom_node(ModTree) of
456464
{true, _} ->
@@ -660,6 +668,27 @@ find_export_entry_pois(EntryPoiKind, Exports) ->
660668
]
661669
).
662670

671+
-spec find_nifs_entry_pois([tree()]) ->
672+
[els_poi:poi()].
673+
find_nifs_entry_pois(Nifs) ->
674+
lists:flatten(
675+
[
676+
case get_name_arity(FATree) of
677+
{F, A} ->
678+
FTree = erl_syntax:arity_qualifier_body(FATree),
679+
poi(
680+
erl_syntax:get_pos(FATree),
681+
nifs_entry,
682+
{F, A},
683+
#{name_range => els_range:range(erl_syntax:get_pos(FTree))}
684+
);
685+
false ->
686+
[]
687+
end
688+
|| FATree <- Nifs
689+
]
690+
).
691+
663692
-spec find_import_entry_pois(tree(), [tree()]) -> [els_poi:poi()].
664693
find_import_entry_pois(ModTree, Imports) ->
665694
M = erl_syntax:atom_value(ModTree),

apps/els_lsp/src/els_range.erl

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ in(#{from := FromA, to := ToA}, #{from := FromB, to := ToB}) ->
3636
els_poi:poi_range().
3737
range({{_Line, _Column} = From, {_ToLine, _ToColumn} = To}, Name, _, _Data) when
3838
Name =:= export;
39+
Name =:= nifs;
3940
Name =:= export_type;
4041
Name =:= spec
4142
->

apps/els_lsp/src/els_references_provider.erl

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ find_references(Uri, #{
7979
Kind =:= implicit_fun;
8080
Kind =:= function;
8181
Kind =:= export_entry;
82-
Kind =:= export_type_entry
82+
Kind =:= export_type_entry;
83+
Kind =:= nifs_entry
8384
->
8485
Key =
8586
case Id of

apps/els_lsp/src/els_rename_provider.erl

+6-3
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ workspace_edits(Uri, [#{kind := Kind} = POI | _], NewName) when
122122
Kind =:= export_entry;
123123
Kind =:= import_entry;
124124
Kind =:= export_type_entry;
125-
Kind =:= type_application
125+
Kind =:= type_application;
126+
Kind =:= nifs_entry
126127
->
127128
case els_code_navigation:goto_definition(Uri, POI) of
128129
{ok, [{DefUri, DefPOI}]} ->
@@ -205,7 +206,8 @@ editable_range(#{kind := Kind, data := #{name_range := Range}}, function) when
205206
Kind =:= export_type_entry;
206207
Kind =:= import_entry;
207208
Kind =:= type_application;
208-
Kind =:= type_definition
209+
Kind =:= type_definition;
210+
Kind =:= nifs_entry
209211
->
210212
%% application POI of a local call and
211213
%% type_application POI of a built-in type don't have name_range data
@@ -267,7 +269,8 @@ changes(Uri, #{kind := function, id := {F, A}}, NewName) ->
267269
|| P <- els_dt_document:pois(Doc, [
268270
export_entry,
269271
function_clause,
270-
spec
272+
spec,
273+
nifs_entry
271274
]),
272275
IsMatch(P)
273276
],

apps/els_lsp/test/els_completion_SUITE.erl

+6
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,12 @@ attributes(Config) ->
231231
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
232232
kind => ?COMPLETION_ITEM_KIND_SNIPPET,
233233
label => <<"-spec">>
234+
},
235+
#{
236+
insertText => <<"nifs([${1:}]).">>,
237+
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
238+
kind => ?COMPLETION_ITEM_KIND_SNIPPET,
239+
label => <<"-nifs().">>
234240
}
235241
] ++ docs_attributes(),
236242
#{result := Completions} =

0 commit comments

Comments
 (0)