Skip to content

Commit

Permalink
Ensure macros that call locals can be invoked locally without failures.
Browse files Browse the repository at this point in the history
  • Loading branch information
José Valim committed Apr 22, 2012
1 parent 4e3d1b2 commit 279357a
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 16 deletions.
16 changes: 2 additions & 14 deletions src/elixir_def.erl
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
-module(elixir_def).
-export([build_table/1,
-export([table/1,
build_table/1,
delete_table/1,
reset_last/1,
local_macro_for/3,
wrap_definition/7,
handle_definition/8,
store_definition/8,
Expand All @@ -28,18 +28,6 @@ delete_table(Module) ->
reset_last(Module) ->
ets:insert(table(Module), { last, [] }).

%% Retrieves a local macro for the given module.
local_macro_for(_Line, _Tuple, #elixir_scope{module=[]}) -> false;

local_macro_for(Line, Tuple, #elixir_scope{module=Module}) ->
case ets:lookup(table(Module), Tuple) of
[{Tuple, _, Kind, _, Clauses}] when Kind == defmacro; Kind == defmacrop ->
Fun = { 'fun', Line, {clauses, lists:reverse(Clauses)} },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result;
_ -> false
end.

%% Wraps the function into a call to store_definition once the function
%% definition is read. The function is compiled into a meta tree to ensure
%% we will receive the full function.
Expand Down
50 changes: 50 additions & 0 deletions src/elixir_def_local.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
%% Module responsible for local invocation of macros and functions.
-module(elixir_def_local).
-export([
% table/1, build_table/1, delete_table/1, format_error/1
macro_for/2,
function_for/3
]).
-include("elixir.hrl").

macro_for(_Tuple, #elixir_scope{module=[]}) -> false;

macro_for(Tuple, #elixir_scope{module=Module}) ->
case ets:lookup(elixir_def:table(Module), Tuple) of
[{Tuple, Line, Kind, _, Clauses}] when Kind == defmacro; Kind == defmacrop ->
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result;
_ -> false
end.

function_for(Module, Name, Arity) ->
Tuple = { Name, Arity },
case ets:lookup(elixir_def:table(Module), Tuple) of
[{Tuple, Line, _, _, Clauses}] ->
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
Result;
_ -> error(ops)
end.

%% Helpers

rewrite_clause({ call, Line, { atom, Line, _ } = Atom, Args }, Module) ->
Remote = { remote, Line,
{ atom, Line, ?MODULE },
{ atom, Line, function_for }
},
Arity = { integer, Line, length(Args) },
FunCall = { call, Line, Remote, [{ atom, Line, Module }, Atom, Arity] },
{ call, Line, FunCall, Args };

rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
list_to_tuple(rewrite_clause(tuple_to_list(Tuple), Module));

rewrite_clause(List, Module) when is_list(List) ->
[rewrite_clause(Item, Module) || Item <- List];

rewrite_clause(Else, _) -> Else.
2 changes: 1 addition & 1 deletion src/elixir_dispatch.erl
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ dispatch_imports(Line, Name, Args, S, Callback) ->
false ->
case find_dispatch(Tuple, S#elixir_scope.macros) of
false ->
Fun = (S#elixir_scope.function /= Tuple) andalso elixir_def:local_macro_for(Line, Tuple, S),
Fun = (S#elixir_scope.function /= Tuple) andalso elixir_def_local:macro_for(Tuple, S),
case Fun of
false -> Callback();
_ -> dispatch_macro_fun(Line, Fun, S#elixir_scope.module, Name, Arity, Args, S)
Expand Down
10 changes: 9 additions & 1 deletion test/elixir/kernel/require_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ defmodule Kernel.RequireTest do
quote do: 1 + 3
end

defmacro my_macro_with_default(value // 5) do
quote do: 1 + unquote(value)
end

test :require_erlang do
require Erlang.lists, as: MyList
assert_equal [1,2,3], MyList.flatten([1,[2],3])
Expand Down Expand Up @@ -52,4 +56,8 @@ defmodule Kernel.RequireTest do
test :locals_and_private_are_always_required do
assert_equal 4, my_private_macro
end
end

test :locals_with_default_are_always_required do
assert_equal 6, my_macro_with_default
end
end

0 comments on commit 279357a

Please sign in to comment.