Skip to content

Commit b5428ad

Browse files
author
José Valim
committed
Add &local/arity and &Remote.fun/arity
1 parent 11093b7 commit b5428ad

File tree

13 files changed

+120
-72
lines changed

13 files changed

+120
-72
lines changed

lib/elixir/lib/inspect.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ defimpl Inspect, for: Function do
364364
mod = fun_info[:module]
365365

366366
if fun_info[:type] == :external and fun_info[:env] == [] do
367-
"function(#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]})"
367+
"&#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]}"
368368
else
369369
case atom_to_list(mod) do
370370
'elixir_compiler_' ++ _ ->

lib/elixir/lib/kernel.ex

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3216,30 +3216,12 @@ defmodule Kernel do
32163216
{ call, line, [left] }
32173217
end
32183218

3219-
defp pipeline_op(left, { call, line, args }=right) when is_list(args) do
3220-
case validate_pipeline_args(args) do
3221-
:error -> pipeline_error(right)
3222-
_ -> nil
3223-
end
3219+
defp pipeline_op(left, { call, line, args }) when is_list(args) do
32243220
{ call, line, [left|args] }
32253221
end
32263222

3227-
defp pipeline_op(left, atom) when is_atom(atom) do
3228-
{ { :., [], [left, atom] }, [], [] }
3229-
end
3230-
3231-
defp pipeline_op(_, other) do
3232-
pipeline_error(other)
3233-
end
3234-
3235-
defp validate_pipeline_args([]), do: nil
3236-
defp validate_pipeline_args([ {:&, _, _ } | _ ]), do: :error
3237-
defp validate_pipeline_args([_|t]) do
3238-
validate_pipeline_args(t)
3239-
end
3240-
3241-
defp pipeline_error(arg) do
3242-
raise ArgumentError, message: "Unsupported expression in pipeline |> operator: #{Macro.to_string arg}"
3223+
defp pipeline_op(_, arg) do
3224+
raise ArgumentError, message: "unsupported expression in pipeline |> operator: #{Macro.to_string arg}"
32433225
end
32443226

32453227
@doc """

lib/elixir/lib/kernel/special_forms.ex

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,27 @@ defmodule Kernel.SpecialForms do
852852
"""
853853
defmacro __block__(args)
854854

855+
@doc """
856+
Captures a call as an anonymous function.
857+
858+
The most common format to capture a function is via module,
859+
name and arity:
860+
861+
iex> fun = &Kernel.is_atom/1
862+
iex> fun.(:atom)
863+
true
864+
iex> fun.("string")
865+
false
866+
867+
Local functions, including private ones, and imported ones
868+
can also be captured by omitting the module name:
869+
870+
&local_function/1
871+
872+
"""
873+
name = :&
874+
defmacro unquote(name)(expr)
875+
855876
@doc """
856877
This is the special form used whenever we have to temporarily
857878
change the scope information of a block. Used when `quote` is

lib/elixir/src/elixir_fn.erl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
-module(elixir_fn).
2+
-export([fn/3, capture/3]).
3+
-import(elixir_scope, [umergec/2]).
4+
-include("elixir.hrl").
5+
6+
capture(Meta, { '/', _, [{ { '.', _, [M, F] }, _ , [] }, A] }, S) when is_atom(F), is_integer(A) ->
7+
{ [MF, FF, AF], SF } = elixir_translator:translate_args([M, F, A], S),
8+
{ { 'fun', ?line(Meta), { function, MF, FF, AF } }, SF };
9+
10+
capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
11+
WrappedMeta =
12+
case lists:keyfind(import_fa, 1, Meta) of
13+
{ import_fa, { Receiver, Context } } ->
14+
lists:keystore(context, 1,
15+
lists:keystore(import, 1, Meta, { import, Receiver }),
16+
{ context, Context }
17+
);
18+
false -> Meta
19+
end,
20+
21+
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
22+
false -> elixir_errors:compile_error(WrappedMeta, S#elixir_scope.file,
23+
"expected ~ts/~B to be a function, but it is a macro", [F, A]);
24+
Else -> Else
25+
end.
26+
27+
fn(Meta, Clauses, S) ->
28+
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
29+
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
30+
elixir_clauses:assigns_block(?line(CMeta), fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
31+
end,
32+
33+
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
34+
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
35+
36+
case length(lists:usort(Arities)) of
37+
1 ->
38+
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
39+
_ ->
40+
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
41+
"cannot mix clauses with different arities in function definition")
42+
end.

lib/elixir/src/elixir_macros.erl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
66
-import(elixir_scope, [umergec/2, umergea/2]).
77
-import(elixir_errors, [compile_error/3, compile_error/4,
8-
syntax_error/3, syntax_error/4,
9-
assert_no_function_scope/3, assert_module_scope/3,
10-
assert_no_match_or_guard_scope/3]).
8+
syntax_error/3, syntax_error/4, assert_no_function_scope/3,
9+
assert_module_scope/3, assert_no_match_or_guard_scope/3]).
1110

1211
-include("elixir.hrl").
1312
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
@@ -50,10 +49,12 @@ translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
5049
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
5150

5251
%% Functions
52+
%% Once this function is removed, the related checks
53+
%% from quote needs to be removed too.
5354

5455
translate({ function, Meta, [[{do,{ '->',_,Pairs}}]] }, S) ->
5556
assert_no_match_or_guard_scope(Meta, 'function', S),
56-
elixir_translator:translate_fn(Meta, Pairs, S);
57+
elixir_fn:fn(Meta, Pairs, S);
5758

5859
translate({ function, _, [{ '/', _, [{{ '.', Meta, [M, F] }, _ , []}, A]}] }, S) when is_atom(F), is_integer(A) ->
5960
translate({ function, Meta, [M, F, A] }, S);

lib/elixir/src/elixir_parser.yrl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ Erlang code.
481481

482482
%% Operators
483483

484-
build_op({ _Kind, Line, '/' }, { '&', _, [{ _Kind, _, Atom } = Left] }, Right) when is_number(Right), is_atom(Atom) ->
484+
build_op({ _Kind, Line, '/' }, { '&', _, [{ Kind, _, Atom } = Left] }, Right) when is_number(Right), is_atom(Atom), is_atom(Kind) ->
485485
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
486486

487487
build_op({ _Kind, Line, '/' }, { '&', _, [{ { '.', _, [_, _] }, _, [] } = Left] }, Right) when is_number(Right) ->
@@ -490,7 +490,7 @@ build_op({ _Kind, Line, '/' }, { '&', _, [{ { '.', _, [_, _] }, _, [] } = Left]
490490
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
491491
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
492492

493-
build_op({ _Kind, Line, Op }, Left, Right) ->
493+
build_op({ _Kind, Line, Op } = A, Left, Right) ->
494494
{ Op, [{line,Line}], [Left, Right] }.
495495

496496
build_unary_op({ _Kind, Line, Op }, Expr) ->

lib/elixir/src/elixir_partials.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.
1111
{ Call, Def, SC } when Def /= [] ->
1212
Final = validate(Meta, Def, SC),
1313
Block = setelement(3, Original, Call),
14-
elixir_translator:translate_fn(Meta, [{ Final, Meta, Block }], SC);
14+
elixir_fn:fn(Meta, [{ Final, Meta, Block }], SC);
1515
_ -> error
1616
end;
1717

lib/elixir/src/elixir_quote.erl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=tr
150150

151151
%% Imports
152152

153+
do_quote({ '&', Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
154+
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
155+
do_quote_fa(function, Meta, Args, F, A, Q, S);
156+
153157
do_quote({ function, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
154158
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
155159
do_quote_fa(function, Meta, Args, F, A, Q, S);

lib/elixir/src/elixir_translator.erl

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
-module(elixir_translator).
44
-export([forms/4, 'forms!'/4]).
55
-export([translate/2, translate_each/2, translate_arg/2,
6-
translate_args/2, translate_apply/7, translate_fn/3]).
6+
translate_args/2, translate_apply/7]).
77
-import(elixir_scope, [umergev/2, umergec/2, umergea/2]).
88
-import(elixir_errors, [syntax_error/3, syntax_error/4,
99
compile_error/3, compile_error/4,
@@ -300,9 +300,13 @@ translate_each({ 'var!', Meta, [_, _] }, S) ->
300300

301301
%% Functions
302302

303+
translate_each({ '&', Meta, [Arg] }, S) ->
304+
assert_no_match_or_guard_scope(Meta, '&', S),
305+
elixir_fn:capture(Meta, Arg, S);
306+
303307
translate_each({ fn, Meta, [[{do, { '->', _, Pairs }}]] }, S) ->
304308
assert_no_match_or_guard_scope(Meta, 'fn', S),
305-
translate_fn(Meta, Pairs, S);
309+
elixir_fn:fn(Meta, Pairs, S);
306310

307311
%% Comprehensions
308312

@@ -562,24 +566,6 @@ no_alias_expansion(Other) ->
562566
is_atom_tuple({ atom, _, _ }) -> true;
563567
is_atom_tuple(_) -> false.
564568

565-
%% Function
566-
567-
translate_fn(Meta, Clauses, S) ->
568-
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
569-
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
570-
elixir_clauses:assigns_block(?line(CMeta), fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
571-
end,
572-
573-
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
574-
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
575-
576-
case length(lists:usort(Arities)) of
577-
1 ->
578-
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
579-
_ ->
580-
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
581-
end.
582-
583569
%% Locals
584570

585571
translate_local(Meta, Name, Args, #elixir_scope{local=nil,function=nil} = S) ->

lib/elixir/test/elixir/inspect_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,12 @@ defmodule Inspect.OthersTest do
225225

226226
test :external_elixir_funs do
227227
bin = inspect(function(Enum.map/2))
228-
assert bin == "function(Enum.map/2)"
228+
assert bin == "&Enum.map/2"
229229
end
230230

231231
test :external_erlang_funs do
232232
bin = inspect(function(:lists.map/2))
233-
assert bin == "function(:lists.map/2)"
233+
assert bin == "&:lists.map/2"
234234
end
235235

236236
test :other_funs do

0 commit comments

Comments
 (0)