diff --git a/apps/els_core/src/els_poi.erl b/apps/els_core/src/els_poi.erl index df425e33..4a82404f 100644 --- a/apps/els_core/src/els_poi.erl +++ b/apps/els_core/src/els_poi.erl @@ -46,6 +46,8 @@ | keyword_expr | macro | module + | nifs + | nifs_entry | parse_transform | record | record_def_field diff --git a/apps/els_lsp/src/els_code_navigation.erl b/apps/els_lsp/src/els_code_navigation.erl index 9bb9355e..b383e6dd 100644 --- a/apps/els_lsp/src/els_code_navigation.erl +++ b/apps/els_lsp/src/els_code_navigation.erl @@ -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 diff --git a/apps/els_lsp/src/els_compiler_diagnostics.erl b/apps/els_lsp/src/els_compiler_diagnostics.erl index 7ae5785f..9465d2da 100644 --- a/apps/els_lsp/src/els_compiler_diagnostics.erl +++ b/apps/els_lsp/src/els_compiler_diagnostics.erl @@ -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 diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl index 8cdd8aae..67689f2b 100644 --- a/apps/els_lsp/src/els_completion_provider.erl +++ b/apps/els_lsp/src/els_completion_provider.erl @@ -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); @@ -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 [] -> @@ -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), @@ -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) -> @@ -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 -> @@ -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 -> @@ -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. @@ -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. diff --git a/apps/els_lsp/src/els_crossref_diagnostics.erl b/apps/els_lsp/src/els_crossref_diagnostics.erl index 10aafd50..9b08a15a 100644 --- a/apps/els_lsp/src/els_crossref_diagnostics.erl +++ b/apps/els_lsp/src/els_crossref_diagnostics.erl @@ -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. diff --git a/apps/els_lsp/src/els_docs.erl b/apps/els_lsp/src/els_docs.erl index d95321e8..3c3b5329 100644 --- a/apps/els_lsp/src/els_docs.erl +++ b/apps/els_lsp/src/els_docs.erl @@ -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), diff --git a/apps/els_lsp/src/els_document_highlight_provider.erl b/apps/els_lsp/src/els_document_highlight_provider.erl index 26b8163e..089e06e2 100644 --- a/apps/els_lsp/src/els_document_highlight_provider.erl +++ b/apps/els_lsp/src/els_document_highlight_provider.erl @@ -122,7 +122,8 @@ kind_groups() -> application, implicit_fun, function, - export_entry + export_entry, + nifs_entry ], %% record [ diff --git a/apps/els_lsp/src/els_dt_references.erl b/apps/els_lsp/src/els_dt_references.erl index 121ac50e..ada5228b 100644 --- a/apps/els_lsp/src/els_dt_references.erl +++ b/apps/els_lsp/src/els_dt_references.erl @@ -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 diff --git a/apps/els_lsp/src/els_parser.erl b/apps/els_lsp/src/els_parser.erl index ea6eb05a..c6d5bc77 100644 --- a/apps/els_lsp/src/els_parser.erl +++ b/apps/els_lsp/src/els_parser.erl @@ -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)), @@ -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, _} -> @@ -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), diff --git a/apps/els_lsp/src/els_range.erl b/apps/els_lsp/src/els_range.erl index 88f26c76..515233e8 100644 --- a/apps/els_lsp/src/els_range.erl +++ b/apps/els_lsp/src/els_range.erl @@ -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 -> diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl index 3421f196..c2048803 100644 --- a/apps/els_lsp/src/els_references_provider.erl +++ b/apps/els_lsp/src/els_references_provider.erl @@ -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 diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl index 69ee3e68..19f1a5ed 100644 --- a/apps/els_lsp/src/els_rename_provider.erl +++ b/apps/els_lsp/src/els_rename_provider.erl @@ -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}]} -> @@ -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 @@ -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) ], diff --git a/apps/els_lsp/test/els_completion_SUITE.erl b/apps/els_lsp/test/els_completion_SUITE.erl index 76db338a..06646cbd 100644 --- a/apps/els_lsp/test/els_completion_SUITE.erl +++ b/apps/els_lsp/test/els_completion_SUITE.erl @@ -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} =