Skip to content

Commit c06a44c

Browse files
author
José Valim
committed
Add Macro.extract_args, closes #677
1 parent 4e8fb99 commit c06a44c

File tree

7 files changed

+63
-4
lines changed

7 files changed

+63
-4
lines changed

lib/elixir/lib/behaviour.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ defmodule Behaviour do
5959
end
6060

6161
defp do_defcallback(fun, return, caller) do
62-
{ name, args } = :elixir_clauses.extract_args(fun)
62+
case Macro.extract_args(fun) do
63+
{ name, args } -> :ok
64+
:error ->
65+
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_binary(fun)}"
66+
end
67+
6368
arity = length(args)
6469

6570
Enum.each args, fn

lib/elixir/lib/kernel.ex

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2942,7 +2942,10 @@ defmodule Kernel do
29422942
append_first = Keyword.get(opts, :append_first, false)
29432943

29442944
lc fun inlist funs do
2945-
{ name, args } = :elixir_clauses.extract_args(fun)
2945+
case Macro.extract_args(fun) do
2946+
{ name, args } -> :ok
2947+
:error -> raise ArgumentError, message: "invalid syntax in defdelegate #{Macro.to_binary(fun)}"
2948+
end
29462949

29472950
actual_args =
29482951
case append_first and args != [] do

lib/elixir/lib/macro.ex

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,28 @@ defmodule Macro do
2929
[:!, :@, :^, :not, :+, :-]
3030
end
3131

32+
@doc """
33+
Receives an expresion representing a possible definition
34+
and extracts its arguments. It returns a tuple with the
35+
function name and the arguments list or `:error` if not
36+
a valid call syntax.
37+
38+
This is useful for macros that want to provide the same
39+
arguments syntax available in def/defp/defmacro and friends.
40+
41+
## Examples
42+
43+
extract_args(quote do: foo) == { :foo, [] }
44+
extract_args(quote do: foo()) == { :foo, [] }
45+
extract_args(quote do: :foo.()) == { :foo, [] }
46+
extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
47+
extract_args(quote do: 1.(1,2,3)) == :error
48+
49+
"""
50+
def extract_args(expr) do
51+
:elixir_clauses.extract_args(expr)
52+
end
53+
3254
@doc """
3355
Recursively escapes the given value so it can be inserted
3456
into a syntax tree. Structures that are valid syntax nodes
@@ -334,6 +356,7 @@ defmodule Macro do
334356
* Macros (local or remote);
335357
* Aliases are expanded (if possible) and return atoms;
336358
* All pseudo-variables (__FILE__, __MODULE__, etc);
359+
* Module attributes reader (@foo);
337360
338361
In case the expression cannot be expanded, it returns the expression itself.
339362

lib/elixir/src/elixir_clauses.erl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ extract_or_clauses(Term, Acc) -> [Term|Acc].
7575

7676
extract_args({ { '.', _, [Name] }, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
7777
extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, [] };
78-
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args }.
78+
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
79+
extract_args(_) -> error.
7980

8081
% Extract guards when it is in the last element of the args
8182

lib/elixir/src/elixir_macros.erl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,21 @@ translate({Kind, Line, [Call]}, S) when ?FUNS(Kind) ->
221221
translate({Kind, Line, [Call, Expr]}, S) when ?FUNS(Kind) ->
222222
assert_module_scope(Line, Kind, S),
223223
assert_no_function_scope(Line, Kind, S),
224+
224225
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
225-
{ Name, Args } = elixir_clauses:extract_args(TCall),
226+
{ Name, Args } = case elixir_clauses:extract_args(TCall) of
227+
error -> syntax_error(Line, S#elixir_scope.file,
228+
"invalid syntax in ~s ~s", [Kind, 'Elixir.Macro':to_binary(TCall)]);
229+
Tuple -> Tuple
230+
end,
231+
226232
assert_no_aliases_name(Line, Name, Args, S),
233+
227234
TName = elixir_tree_helpers:abstract_syntax(Name),
228235
TArgs = elixir_tree_helpers:abstract_syntax(Args),
229236
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
230237
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
238+
231239
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, S), S };
232240

233241
translate({Kind, Line, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->

lib/elixir/test/elixir/kernel/errors_test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ defmodule Kernel.ErrorsTest do
167167
format_rescue 'defmodule Foo, do: (defmodule Elixir.Foo, do: true)'
168168
end
169169

170+
test :invalid_definition do
171+
assert "nofile:1: invalid syntax in def 1.(hello)" ==
172+
format_rescue 'defmodule Foo, do: (def 1.(hello), do: true)'
173+
end
174+
170175
test :duplicated_bitstring_size do
171176
assert "nofile:1: duplicated size definition for bitstring" == format_rescue '<<1 :: [size(12), size(13)]>>'
172177
end

lib/elixir/test/elixir/macro_test.exs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ defmodule MacroTest do
141141
assert Macro.to_binary(quote do: foo.bar.([1, 2, 3])) == "foo.bar().([1, 2, 3])"
142142
end
143143

144+
test :atom_call_to_binary do
145+
assert Macro.to_binary(quote do: :foo.(1, 2, 3)) == ":foo.(1, 2, 3)"
146+
end
147+
144148
test :aliases_call_to_binary do
145149
assert Macro.to_binary(quote do: Foo.Bar.baz(1, 2, 3)) == "Foo.Bar.baz(1, 2, 3)"
146150
assert Macro.to_binary(quote do: Foo.Bar.baz([1, 2, 3])) == "Foo.Bar.baz([1, 2, 3])"
@@ -267,4 +271,14 @@ defmodule MacroTest do
267271
assert Macro.safe_term(quote do: [1+1]) == { :unsafe, quote do: 1 + 1 }
268272
assert Macro.safe_term(quote do: {1+1}) == { :unsafe, quote do: 1 + 1 }
269273
end
274+
275+
## extract_args
276+
277+
test :extract_args do
278+
assert Macro.extract_args(quote do: foo) == { :foo, [] }
279+
assert Macro.extract_args(quote do: foo()) == { :foo, [] }
280+
assert Macro.extract_args(quote do: :foo.()) == { :foo, [] }
281+
assert Macro.extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
282+
assert Macro.extract_args(quote do: 1.(1,2,3)) == :error
283+
end
270284
end

0 commit comments

Comments
 (0)