Merge pull request #3666 from lexmag/import-many-friends

Introduce support for Mod.{A, B, C} on imports, alias, require and use
This commit is contained in:
José Valim
2015-09-25 12:02:30 +02:00
7 changed files with 152 additions and 8 deletions
+21 -7
View File
@@ -3613,17 +3613,31 @@ defmodule Kernel do
please define it a module which will be imported accordingly.
"""
defmacro use(module, opts \\ []) do
expanded = Macro.expand(module, __CALLER__)
case is_atom(expanded) do
false ->
raise ArgumentError, "invalid arguments for use, expected an atom or alias as argument"
true ->
calls = Enum.map(expand_aliases(module, __CALLER__), fn
expanded when is_atom(expanded) ->
quote do
require unquote(expanded)
unquote(expanded).__using__(unquote(opts))
end
end
_otherwise ->
raise ArgumentError, "invalid arguments for use, expected an atom or alias as argument"
end)
quote(do: (unquote_splicing calls))
end
defp expand_aliases({{:., _, [base, :{}]}, _, refs}, env) do
base = Macro.expand(base, env)
Enum.map(refs, fn
{:__aliases__, _, ref} ->
Module.concat([base | ref])
ref when is_atom(ref) ->
Module.concat(base, ref)
other -> other
end)
end
defp expand_aliases(module, env) do
[Macro.expand(module, env)]
end
@doc """
+28
View File
@@ -46,6 +46,20 @@ expand({'__aliases__', _, _} = Alias, E) ->
%% alias
expand({Kind, Meta, [{{'.', _, [Base, '{}']}, _, Refs} | Rest]}, E)
when Kind == alias; Kind == require; Kind == import ->
case Rest of
[] ->
expand_multi_alias_call(Kind, Meta, Base, Refs, [], E);
[Opts] ->
case lists:keymember(as, 1, Opts) of
true ->
compile_error(Meta, ?m(E, file),
":as option is not supported by multi-alias call");
false ->
expand_multi_alias_call(Kind, Meta, Base, Refs, Opts, E)
end
end;
expand({alias, Meta, [Ref]}, E) ->
expand({alias, Meta, [Ref, []]}, E);
expand({alias, Meta, [Ref, KV]}, E) ->
@@ -362,6 +376,20 @@ expand(Other, E) ->
%% Helpers
expand_multi_alias_call(Kind, Meta, Base, Refs, Opts, E) ->
{BaseRef, EB} = expand_without_aliases_report(Base, E),
Fun = fun
({'__aliases__', _, Ref}, ER) ->
expand({Kind, Meta, [elixir_aliases:concat([BaseRef | Ref]), Opts]}, ER);
(Ref, ER) when is_atom(Ref) ->
expand({Kind, Meta, [elixir_aliases:concat([BaseRef, Ref]), Opts]}, ER);
(Other, _ER) ->
compile_error(Meta, ?m(E, file),
"invalid argument for ~ts, expected a compile time atom or alias, got: ~ts",
[atom_to_list(Kind), 'Elixir.Macro':to_string(Other)])
end,
lists:mapfoldl(Fun, EB, Refs).
expand_list([{'|', Meta, [_, _] = Args}], Fun, Acc, List) ->
{EArgs, EAcc} = lists:mapfoldl(Fun, Acc, Args),
expand_list([], Fun, EAcc, [{'|', Meta, EArgs}|List]);
+10 -1
View File
@@ -21,7 +21,8 @@ Nonterminals
call_args_no_parens_many_strict
stab stab_eoe stab_expr stab_maybe_expr stab_parens_many
kw_eol kw_base kw call_args_no_parens_kw_expr call_args_no_parens_kw
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
dot_op dot_alias dot_alias_container
dot_identifier dot_op_identifier dot_do_identifier
dot_paren_identifier dot_bracket_identifier
do_block fn_eoe do_eoe end_eoe block_eoe block_item block_list
.
@@ -407,6 +408,10 @@ dot_identifier -> matched_expr dot_op identifier : build_dot('$2', '$1', '$3').
dot_alias -> aliases : {'__aliases__', meta_from_token('$1', 0), ?exprs('$1')}.
dot_alias -> matched_expr dot_op aliases : build_dot_alias('$2', '$1', '$3').
dot_alias -> matched_expr dot_op dot_alias_container : build_dot_container('$2', '$1', '$3').
dot_alias_container -> open_curly '}' : [].
dot_alias_container -> open_curly container_args close_curly : '$2'.
dot_op_identifier -> op_identifier : '$1'.
dot_op_identifier -> matched_expr dot_op op_identifier : build_dot('$2', '$1', '$3').
@@ -634,6 +639,10 @@ build_dot_alias(_Dot, Atom, {'aliases', _, _} = Token) when is_atom(Atom) ->
build_dot_alias(Dot, Other, {'aliases', _, Right}) ->
{'__aliases__', meta_from_token(Dot), [Other|Right]}.
build_dot_container(Dot, Left, Right) ->
Meta = meta_from_token(Dot),
{{'.', Meta, [Left, '{}']}, Meta, Right}.
build_dot(Dot, Left, Right) ->
{'.', meta_from_token(Dot), [Left, extract_identifier(Right)]}.
@@ -44,6 +44,14 @@ defmodule Kernel.AliasTest do
test "nested elixir alias" do
assert Kernel.AliasTest.Elixir.sample == 1
end
test "multi-call" do
alias unquote(Inspect).{
Opts, Algebra,
}
assert %Opts{} == %Inspect.Opts{}
assert Algebra.empty == :doc_nil
end
end
defmodule Kernel.AliasNestingGenerator do
@@ -838,6 +838,20 @@ defmodule Kernel.ErrorsTest do
'''
end
test "bad multi-call" do
assert_compile_fail CompileError,
"nofile:1: invalid argument for alias, expected a compile time atom or alias, got: 42",
'alias IO.{ANSI, 42}'
assert_compile_fail CompileError,
"nofile:1: :as option is not supported by multi-alias call",
'alias Elixir.{Map}, as: Dict'
assert_compile_fail UndefinedFunctionError,
"undefined function: List.{}/1",
'[List.{Chars}, "one"]'
end
test "macros error stacktrace" do
assert [{:erlang, :+, [1, :foo], _},
{Kernel.ErrorsTest.MacrosErrorStacktrace, :sample, 1, _}|_] =
@@ -12,6 +12,24 @@ defmodule Kernel.ImportTest do
end
end
test "multi-call" do
import Elixir.{List, unquote(:String)}
assert keymember?([a: 1], :a, 0)
assert valid_character?("ø")
end
test "blank multi-call" do
import List.{}
# Buggy local duplicate is untouched
assert duplicate([1], 2) == [1]
end
test "multi-call with options" do
import Elixir.{List}, only: []
# Buggy local duplicate is untouched
assert duplicate([1], 2) == [1]
end
test "import all" do
import :lists
assert flatten([1, [2], 3]) == [1, 2, 3]
+53
View File
@@ -647,4 +647,57 @@ defmodule KernelTest do
defp a_list, do: [1, 2, 3]
defp a_nil, do: nil
end
defmodule UseMacro do
use ExUnit.Case, async: true
import ExUnit.CaptureIO
defmodule SampleA do
defmacro __using__(opts) do
prefix = Keyword.get(opts, :prefix, "")
IO.puts(prefix <> "A")
end
end
defmodule SampleB do
defmacro __using__(_) do
IO.puts("B")
end
end
test "invalid argument" do
message = "invalid arguments for use, expected an atom or alias as argument"
assert_raise ArgumentError, message, fn ->
Code.eval_string("use 42")
end
end
test "multi-call" do
assert capture_io(fn ->
Code.eval_string("use UseMacro.{SampleA, SampleB,}", [], __ENV__)
end) == "A\nB\n"
end
test "multi-call with options" do
assert capture_io(fn ->
Code.eval_string(~S|use UseMacro.{SampleA}, prefix: "-"|, [], __ENV__)
end) == "-A\n"
end
test "multi-call with unquote" do
assert capture_io(fn ->
Code.eval_string("""
defmodule TestMod do
def main() do
use UseMacro.{SampleB, unquote(:SampleA)}
end
end
""", [], __ENV__)
end) == "B\nA\n"
after
:code.purge(UseMacro.TestMod)
:code.delete(UseMacro.TestMod)
end
end
end