Skip to content

Commit dc59a83

Browse files
committed
nifs attribute completion
1 parent 6410bf6 commit dc59a83

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
[] ->
@@ -495,6 +508,7 @@ attributes(Document, Line) ->
495508
snippet(attribute_include),
496509
snippet(attribute_include_lib),
497510
snippet(attribute_on_load),
511+
snippet(attribute_nifs),
498512
snippet(attribute_opaque),
499513
snippet(attribute_record),
500514
snippet(attribute_type),
@@ -579,6 +593,11 @@ snippet(attribute_on_load) ->
579593
<<"-on_load().">>,
580594
<<"on_load(${1:Function}).">>
581595
);
596+
snippet(attribute_nifs) ->
597+
snippet(
598+
<<"-nifs().">>,
599+
<<"nifs([${1:}]).">>
600+
);
582601
snippet(attribute_export_type) ->
583602
snippet(<<"-export_type().">>, <<"export_type([${1:}]).">>);
584603
snippet(attribute_feature) ->
@@ -787,7 +806,7 @@ definitions(Document, POIKind, ItemFormat, ExportedOnly) ->
787806
{item_format(), els_poi:poi_kind() | any}.
788807
completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
789808
ItemFormat =
790-
case is_in_export(Document, Line, Column) of
809+
case is_in_mfa_list_attr(Document, Line, Column) of
791810
true ->
792811
arity_only;
793812
false ->
@@ -811,7 +830,7 @@ completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
811830
true ->
812831
type_definition;
813832
false ->
814-
case is_in(Document, Line, Column, [export, function]) of
833+
case is_in(Document, Line, Column, [export, nifs, function]) of
815834
true ->
816835
function;
817836
false ->
@@ -820,25 +839,30 @@ completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
820839
end,
821840
{ItemFormat, POIKind}.
822841

823-
-spec is_in_export(els_dt_document:item(), line(), column()) -> boolean().
824-
is_in_export(#{text := Text} = Document, Line, Column) ->
825-
%% Sometimes is_in will be confused because -export() failed to be parsed.
842+
-spec is_in_mfa_list_attr(els_dt_document:item(), line(), column()) -> boolean().
843+
is_in_mfa_list_attr(#{text := Text} = Document, Line, Column) ->
844+
%% Sometimes is_in will be confused because e.g. -export() failed to be parsed.
826845
%% In such case we can use a heuristic to determine if we are inside
827846
%% an export.
828-
is_in(Document, Line, Column, [export, export_type]) orelse
829-
is_in_export_heuristic(Text, Line - 1).
847+
is_in(Document, Line, Column, [export, export_type, nifs]) orelse
848+
is_in_mfa_list_attr_heuristic(Text, Line - 1).
849+
850+
-spec is_in_mfa_list_attr_heuristic(binary(), line()) -> boolean().
851+
is_in_mfa_list_attr_heuristic(Text, Line) ->
852+
is_in_heuristic(Text, <<"export">>, Line) orelse
853+
is_in_heuristic(Text, <<"nifs">>, Line).
830854

831-
-spec is_in_export_heuristic(binary(), line()) -> boolean().
832-
is_in_export_heuristic(Text, Line) ->
855+
-spec is_in_heuristic(binary(), binary(), line()) -> boolean().
856+
is_in_heuristic(Text, Attr, Line) ->
857+
Len = byte_size(Attr),
833858
case els_text:line(Text, Line) of
834-
<<"-export", _/binary>> ->
835-
%% In export
859+
<<"-", Attr:Len/binary, _/binary>> ->
860+
%% In Attr
836861
true;
837862
<<" ", _/binary>> when Line > 1 ->
838863
%% Indented line, continue to search previous line
839-
is_in_export_heuristic(Text, Line - 1);
864+
is_in_heuristic(Text, Attr, Line - 1);
840865
_ ->
841-
%% Not in export
842866
false
843867
end.
844868

@@ -1409,12 +1433,12 @@ is_exported_heuristic_test_() ->
14091433
"-define(FOO, foo).\n"
14101434
>>,
14111435
[
1412-
?_assertEqual(false, is_in_export_heuristic(Text, 0)),
1413-
?_assertEqual(true, is_in_export_heuristic(Text, 1)),
1414-
?_assertEqual(true, is_in_export_heuristic(Text, 2)),
1415-
?_assertEqual(true, is_in_export_heuristic(Text, 3)),
1416-
?_assertEqual(true, is_in_export_heuristic(Text, 4)),
1417-
?_assertEqual(false, is_in_export_heuristic(Text, 5))
1436+
?_assertEqual(false, is_in_mfa_list_attr_heuristic(Text, 0)),
1437+
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 1)),
1438+
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 2)),
1439+
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 3)),
1440+
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 4)),
1441+
?_assertEqual(false, is_in_mfa_list_attr_heuristic(Text, 5))
14181442
].
14191443

14201444
-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)