<commit_before>%% Module responsible for handling imports and conflicts
%% between local functions and imports.
%% For imports dispatch, please check elixir_dispatch.
-module(elixir_import).
-export([import/4, special_form/2, format_error/1]).
-include("elixir.hrl").

import(Meta, Ref, Opts, E) ->
  {Functions, Macros, Added} =
    case keyfind(only, Opts) of
      {only, functions} ->
        {Added1, Funs} = import_functions(Meta, Ref, Opts, E),
        {Funs, keydelete(Ref, ?key(E, macros)), Added1};
      {only, macros} ->
        {Added2, Macs} = import_macros(true, Meta, Ref, Opts, E),
        {keydelete(Ref, ?key(E, functions)), Macs, Added2};
      {only, List} when is_list(List) ->
        {Added1, Funs} = import_functions(Meta, Ref, Opts, E),
        {Added2, Macs} = import_macros(false, Meta, Ref, Opts, E),
        {Funs, Macs, Added1 or Added2};
      false ->
        {Added1, Funs} = import_functions(Meta, Ref, Opts, E),
        {Added2, Macs} = import_macros(false, Meta, Ref, Opts, E),
        {Funs, Macs, Added1 or Added2}
    end,

  record_warn(Meta, Ref, Opts, Added, E),
  {Functions, Macros}.

import_functions(Meta, Ref, Opts, E) ->
  calculate(Meta, Ref, Opts, ?key(E, functions), ?key(E, file), fun() ->
    get_functions(Ref)
  end).

import_macros(Force, Meta, Ref, Opts, E) ->
  calculate(Meta, Ref, Opts, ?key(E, macros), ?key(E, file), fun() ->
    case fetch_macros(Ref) of
      {ok, Macros} ->
        Macros;
      error when Force ->
        Tuple = {no_macros, Ref},
        elixir_errors:form_error(Meta, ?key(E, file), ?MODULE, Tuple);
      error ->
        []
    end
  end).

record_warn(Meta, Ref, Opts, Added, E) ->
  Warn =
    case keyfind(warn, Opts) of
      {warn, false} -> false;
      {warn, true} -> true;
      false -> not lists:keymember(context, 1, Meta)
    end,

  Only =
    case keyfind(only, Opts) of
      {only, List} when is_list(List) -> List;
      _ -> []
    end,

  elixir_lexical:record_import(Ref, Only, ?line(Meta), Added and Warn, ?key(E, lexical_tracker)).

%% Calculates the imports based on only and except

calculate(Meta, Key, Opts, Old, File, Existing) ->
  New = case keyfind(only, Opts) of
    {only, Only} when is_list(Only) ->
      ok = ensure_keyword_list(Meta, File, Only, only),
      case keyfind(except, Opts) of
        false ->
          ok;
        _ ->
          elixir_errors:compile_error(Meta, File,
            ":only and :except can only be given together to import"
            " when :only is either :functions or :macros")
      end,
      case Only -- get_exports(Key) of
        [{Name, Arity} | _] ->
          Tuple = {invalid_import, {Key, Name, Arity}},
          elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
        _ ->
          intersection(Only, Existing())
      end;
    _ ->
      case keyfind(except, Opts) of
        false ->
          remove_underscored(Existing());
        {except, Except} when is_list(Except) ->
          ok = ensure_keyword_list(Meta, File, Except, except),
          %% We are not checking existence of exports listed in :except option
          %% on purpose: to support backwards compatible code.
          %% For example, "import String, except: [trim: 1]"
          %% should work across all Elixir versions.
          case keyfind(Key, Old) of
            false -> remove_underscored(Existing()) -- Except;
            {Key, OldImports} -> OldImports -- Except
          end
      end
  end,

  %% Normalize the data before storing it
  Set   = ordsets:from_list(New),
  Final = remove_internals(Set),

  case Final of
    [] ->
      {false, keydelete(Key, Old)};
    _  ->
      ensure_no_special_form_conflict(Meta, File, Key, Final),
      {true, [{Key, Final} | keydelete(Key, Old)]}
  end.

%% Retrieve functions and macros from modules

get_exports(Module) ->
  get_functions(Module) ++ get_macros(Module).

get_functions(Module) ->
  try
    Module:'__info__'(functions)
  catch
    error:undef -> Module:module_info(exports)
  end.

get_macros(Module) ->
  case fetch_macros(Module) of
    {ok, Macros} ->
      Macros;
    error ->
      []
  end.

fetch_macros(Module) ->
  try
    {ok, Module:'__info__'(macros)}
  catch
    error:undef -> error
  end.

%% VALIDATION HELPERS

ensure_no_special_form_conflict(Meta, File, Key, [{Name, Arity} | T]) ->
  case special_form(Name, Arity) of
    true  ->
      Tuple = {special_form_conflict, {Key, Name, Arity}},
      elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
    false ->
      ensure_no_special_form_conflict(Meta, File, Key, T)
  end;

ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.

ensure_keyword_list(_Meta, _File, [], _Kind) -> ok;

ensure_keyword_list(Meta, File, [{Key, Value} | Rest], Kind) when is_atom(Key), is_integer(Value) ->
  ensure_keyword_list(Meta, File, Rest, Kind);

ensure_keyword_list(Meta, File, _Other, Kind) ->
  Message =
    "invalid :~s option for import, "
    "expected a keyword list with integer values",
  elixir_errors:compile_error(Meta, File, Message, [Kind]).

%% ERROR HANDLING

format_error({invalid_import, {Receiver, Name, Arity}}) ->
  io_lib:format("cannot import ~ts.~ts/~B because it is undefined or private",
    [elixir_aliases:inspect(Receiver), Name, Arity]);

format_error({special_form_conflict, {Receiver, Name, Arity}}) ->
  io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
    [elixir_aliases:inspect(Receiver), Name, Arity]);
<commit_msg>Replace compile_error/4 with form_error/4 in elixir_import (#5916)<commit_after>%% Module responsible for handling imports and conflicts
%% between local functions and imports.
%% For imports dispatch, please check elixir_dispatch.
-module(elixir_import).
-export([import/4, special_form/2, format_error/1]).
-include("elixir.hrl").

import(Meta, Ref, Opts, E) ->
  {Functions, Macros, Added} =
    case keyfind(only, Opts) of
      {only, functions} ->
        {Added1, Funs} = import_functions(Meta, Ref, Opts, E),
        {Funs, keydelete(Ref, ?key(E, macros)), Added1};
      {only, macros} ->
        {Added2, Macs} = import_macros(true, Meta, Ref, Opts, E),
        {keydelete(Ref, ?key(E, functions)), Macs, Added2};
      {only, List} when is_list(List) ->
        {Added1, Funs} = import_functions(Meta, Ref, Opts, E),
        {Added2, Macs} = import_macros(false, Meta, Ref, Opts, E),
        {Funs, Macs, Added1 or Added2};
      false ->
        {Added1, Funs} = import_functions(Meta, Ref, Opts, E),
        {Added2, Macs} = import_macros(false, Meta, Ref, Opts, E),
        {Funs, Macs, Added1 or Added2}
    end,

  record_warn(Meta, Ref, Opts, Added, E),
  {Functions, Macros}.

import_functions(Meta, Ref, Opts, E) ->
  calculate(Meta, Ref, Opts, ?key(E, functions), ?key(E, file), fun() ->
    get_functions(Ref)
  end).

import_macros(Force, Meta, Ref, Opts, E) ->
  calculate(Meta, Ref, Opts, ?key(E, macros), ?key(E, file), fun() ->
    case fetch_macros(Ref) of
      {ok, Macros} ->
        Macros;
      error when Force ->
        elixir_errors:form_error(Meta, ?key(E, file), ?MODULE, {no_macros, Ref});
      error ->
        []
    end
  end).

record_warn(Meta, Ref, Opts, Added, E) ->
  Warn =
    case keyfind(warn, Opts) of
      {warn, false} -> false;
      {warn, true} -> true;
      false -> not lists:keymember(context, 1, Meta)
    end,

  Only =
    case keyfind(only, Opts) of
      {only, List} when is_list(List) -> List;
      _ -> []
    end,

  elixir_lexical:record_import(Ref, Only, ?line(Meta), Added and Warn, ?key(E, lexical_tracker)).

%% Calculates the imports based on only and except

calculate(Meta, Key, Opts, Old, File, Existing) ->
  New = case keyfind(only, Opts) of
    {only, Only} when is_list(Only) ->
      ok = ensure_keyword_list(Meta, File, Only, only),
      case keyfind(except, Opts) of
        false ->
          ok;
        _ ->
          elixir_errors:form_error(Meta, File, ?MODULE, only_and_except_given)
      end,
      case Only -- get_exports(Key) of
        [{Name, Arity} | _] ->
          elixir_errors:form_error(Meta, File, ?MODULE, {invalid_import, {Key, Name, Arity}});
        _ ->
          intersection(Only, Existing())
      end;
    _ ->
      case keyfind(except, Opts) of
        false ->
          remove_underscored(Existing());
        {except, Except} when is_list(Except) ->
          ok = ensure_keyword_list(Meta, File, Except, except),
          %% We are not checking existence of exports listed in :except option
          %% on purpose: to support backwards compatible code.
          %% For example, "import String, except: [trim: 1]"
          %% should work across all Elixir versions.
          case keyfind(Key, Old) of
            false -> remove_underscored(Existing()) -- Except;
            {Key, OldImports} -> OldImports -- Except
          end
      end
  end,

  %% Normalize the data before storing it
  Set   = ordsets:from_list(New),
  Final = remove_internals(Set),

  case Final of
    [] ->
      {false, keydelete(Key, Old)};
    _  ->
      ensure_no_special_form_conflict(Meta, File, Key, Final),
      {true, [{Key, Final} | keydelete(Key, Old)]}
  end.

%% Retrieve functions and macros from modules

get_exports(Module) ->
  get_functions(Module) ++ get_macros(Module).

get_functions(Module) ->
  try
    Module:'__info__'(functions)
  catch
    error:undef -> Module:module_info(exports)
  end.

get_macros(Module) ->
  case fetch_macros(Module) of
    {ok, Macros} ->
      Macros;
    error ->
      []
  end.

fetch_macros(Module) ->
  try
    {ok, Module:'__info__'(macros)}
  catch
    error:undef -> error
  end.

%% VALIDATION HELPERS

ensure_no_special_form_conflict(Meta, File, Key, [{Name, Arity} | T]) ->
  case special_form(Name, Arity) of
    true  ->
      elixir_errors:form_error(Meta, File, ?MODULE, {special_form_conflict, {Key, Name, Arity}});
    false ->
      ensure_no_special_form_conflict(Meta, File, Key, T)
  end;

ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.

ensure_keyword_list(_Meta, _File, [], _Kind) -> ok;

ensure_keyword_list(Meta, File, [{Key, Value} | Rest], Kind) when is_atom(Key), is_integer(Value) ->
  ensure_keyword_list(Meta, File, Rest, Kind);

ensure_keyword_list(Meta, File, _Other, Kind) ->
  elixir_errors:form_error(Meta, File, ?MODULE, {invalid_option, Kind}).

%% ERROR HANDLING

format_error(only_and_except_given) ->
  ":only and :except can only be given together to import "
  "when :only is either :functions or :macros";

format_error({invalid_import, {Receiver, Name, Arity}}) ->
  io_lib:format("cannot import ~ts.~ts/~B because it is undefined or private",
    [elixir_aliases:inspect(Receiver), Name, Arity]);

format_error({invalid_option, Option}) ->
  Message = "invalid :~s option for import, expected a keyword list with integer values",
  io_lib:format(Message, [Option]);

format_error({special_form_conflict, {Receiver, Name, Arity}}) ->
  io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
    [elixir_aliases:inspect(Receiver), Name, Arity]);

{'fraction_non_alphanumeric': 0.13953488372093023, 'fraction_numerical': 0.0036055525509284298, 'mean_word_length': 3.0986331732545254, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 101, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4801409', 'n_tokens_mistral': 3834, 'n_tokens_neox': 3758, 'n_words': 998}