Skip to content

Commit 2d8259b

Browse files
author
José Valim
committed
Support (x, y) when z, closes #1313
1 parent ae03e10 commit 2d8259b

File tree

7 files changed

+53
-24
lines changed

7 files changed

+53
-24
lines changed

lib/elixir/lib/macro.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@ defmodule Macro do
248248
op_to_string(left) <> " #{op} " <> op_to_string(right)
249249
end
250250

251+
# Splat when
252+
def to_string({ :when, _, args }) do
253+
{ left, right } = :elixir_tree_helpers.split_last(args)
254+
"(" <> Enum.map_join(left, ", ", to_string(&1)) <> ") when " <> to_string(right)
255+
end
256+
251257
# Unary ops
252258
def to_string({ :not, _, [arg] }) do
253259
"not " <> to_string(arg)

lib/elixir/src/elixir_clauses.erl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
%% fn, receive and friends. try is handled in elixir_try.
33
-module(elixir_clauses).
44
-export([
5-
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
5+
assigns/3, assigns_block/5, assigns_block/6, extract_splat_guards/1,
66
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
77
-include("elixir.hrl").
88

@@ -80,13 +80,13 @@ extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, []
8080
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
8181
extract_args(_) -> error.
8282

83-
% Extract guards when it is in the last element of the args
83+
% Extract guards when multiple left side args are allowed.
8484

85-
extract_last_guards([]) -> { [], [] };
86-
extract_last_guards(Args) ->
87-
{ Left, Right } = elixir_tree_helpers:split_last(Args),
88-
{ Bare, Guards } = extract_guards(Right),
89-
{ Left ++ [Bare], Guards }.
85+
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
86+
{ Left, Right } = elixir_tree_helpers:split_last(Args),
87+
{ Left, extract_or_clauses(Right, []) };
88+
extract_splat_guards(Else) ->
89+
{ Else, [] }.
9090

9191
% Function for translating macros with match style like case and receive.
9292

lib/elixir/src/elixir_parser.yrl

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,12 @@ stab_eol -> stab eol : '$1'.
218218

219219
stab_expr -> expr : '$1'.
220220
stab_expr -> stab_op stab_maybe_expr : build_op('$1', [], '$2').
221-
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
222-
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
221+
stab_expr -> call_args_no_parens stab_op stab_maybe_expr :
222+
build_op('$2', unwrap_when(unwrap_splice('$1')), '$3').
223+
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr :
224+
build_op('$2', unwrap_splice('$1'), '$3').
225+
stab_expr -> call_args_parens_not_one when_op matched_expr stab_op stab_maybe_expr :
226+
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
223227

224228
stab_maybe_expr -> 'expr' : '$1'.
225229
stab_maybe_expr -> '$empty' : nil.
@@ -239,7 +243,7 @@ open_paren -> '(' eol : '$1'.
239243
close_paren -> ')' : '$1'.
240244
close_paren -> eol ')' : '$2'.
241245

242-
empty_paren -> open_paren ')' : nil.
246+
empty_paren -> open_paren ')' : '$1'.
243247

244248
open_bracket -> '[' : '$1'.
245249
open_bracket -> '[' eol : '$1'.
@@ -394,15 +398,15 @@ matched_comma_expr -> matched_expr : ['$1'].
394398
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
395399

396400
call_args_no_parens_strict -> call_args_no_parens : '$1'.
397-
call_args_no_parens_strict -> open_paren ')' : throw_no_parens_strict('$1').
401+
call_args_no_parens_strict -> empty_paren : throw_no_parens_strict('$1').
398402
call_args_no_parens_strict -> open_paren matched_kw_base close_paren : throw_no_parens_strict('$1').
399403
call_args_no_parens_strict -> open_paren matched_expr ',' call_args_no_parens close_paren : throw_no_parens_strict('$1').
400404

401405
call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
402406
call_args_no_parens -> matched_kw_base : ['$1'].
403407
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
404408

405-
call_args_parens_not_one -> open_paren ')' : [].
409+
call_args_parens_not_one -> empty_paren : [].
406410
call_args_parens_not_one -> open_paren matched_kw_base close_paren : ['$2'].
407411
call_args_parens_not_one -> open_paren matched_expr ',' call_args_no_parens close_paren : ['$2'|'$4'].
408412

@@ -420,7 +424,7 @@ optional_comma_expr -> paren_expr ',' : '$1'.
420424

421425
call_args -> comma_expr : lists:reverse('$1').
422426

423-
call_args_parens -> open_paren ')' : [].
427+
call_args_parens -> empty_paren : [].
424428
call_args_parens -> open_paren call_args close_paren : '$2'.
425429

426430
% KV
@@ -597,7 +601,7 @@ build_stab(Meta, [], Marker, Temp, Acc) ->
597601

598602
%% Every time the parser sees a (unquote_splicing())
599603
%% it assumes that a block is being spliced, wrapping
600-
%% the splicing in a __block__. But in the stab cause,
604+
%% the splicing in a __block__. But in the stab clause,
601605
%% we can have (unquote_splicing(1,2,3)) -> :ok, in such
602606
%% case, we don't actually want the block, since it is
603607
%% an arg style call. unwrap_splice unwraps the splice
@@ -607,6 +611,14 @@ unwrap_splice([{ '__block__', [], [{ unquote_splicing, _, _ }] = Splice }]) ->
607611

608612
unwrap_splice(Other) -> Other.
609613

614+
unwrap_when(Args) ->
615+
case elixir_tree_helpers:split_last(Args) of
616+
{ Start, { 'when', Meta, [_, _] = End } } ->
617+
[{ 'when', Meta, Start ++ End }];
618+
{ _, _ } ->
619+
Args
620+
end.
621+
610622
%% Errors
611623

612624
throw(Line, Error, Token) ->

lib/elixir/src/elixir_translator.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ no_alias_expansion(Other) ->
551551

552552
translate_fn(Meta, Clauses, S) ->
553553
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
554-
{ Args, Guards } = elixir_clauses:extract_last_guards(ArgsWithGuards),
554+
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
555555
elixir_clauses:assigns_block(?line(CMeta), fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
556556
end,
557557

lib/elixir/src/elixir_try.erl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ clauses(Meta, Clauses, S) ->
1010
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
1111

1212
each_clause({ 'catch', Meta, Raw, Expr }, S) ->
13-
{ Args, Guards } = elixir_clauses:extract_last_guards(Raw),
13+
{ Args, Guards } = elixir_clauses:extract_splat_guards(Raw),
1414

1515
Final = case Args of
1616
[X] -> [throw, X, { '_', Meta, nil }];
@@ -23,19 +23,19 @@ each_clause({ 'catch', Meta, Raw, Expr }, S) ->
2323
Condition = { '{}', Meta, Final },
2424
elixir_clauses:assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
2525

26-
each_clause({ rescue, Meta, [Condition|T], Expr }, S) ->
26+
each_clause({ rescue, Meta, [Condition], Expr }, S) ->
2727
case normalize_rescue(Meta, Condition, S) of
2828
{ Left, Right } ->
2929
case Left of
3030
{ '_', _, _ } ->
3131
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
3232
{ Clause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
33-
each_clause({ 'catch', Meta, [error, Clause|T], Expr }, CS);
33+
each_clause({ 'catch', Meta, Clause, Expr }, CS);
3434
_ ->
3535
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
3636
case Safe of
3737
true ->
38-
each_clause({ 'catch', Meta, [error, Clause|T], Expr }, S);
38+
each_clause({ 'catch', Meta, Clause, Expr }, S);
3939
false ->
4040
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
4141
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
@@ -44,11 +44,11 @@ each_clause({ rescue, Meta, [Condition|T], Expr }, S) ->
4444
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
4545
] },
4646
FinalExpr = prepend_to_block(Meta, Match, Expr),
47-
each_clause({ 'catch', Meta, [error, FinalClause|T], FinalExpr }, CS)
47+
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
4848
end
4949
end;
5050
_ ->
51-
each_clause({ 'catch', Meta, [error, Condition|T], Expr }, S)
51+
each_clause({ 'catch', Meta, [error, Condition], Expr }, S)
5252
end;
5353

5454
each_clause({rescue,Meta,_,_}, S) ->
@@ -97,7 +97,7 @@ normalize_rescue(Meta, Condition, S) ->
9797
end.
9898

9999
%% Convert rescue clauses into guards.
100-
rescue_guards(_, Var, nil, _) -> { Var, false };
100+
rescue_guards(_, Var, nil, _) -> { [error, Var], false };
101101

102102
rescue_guards(Meta, Var, Guards, S) ->
103103
{ RawElixir, RawErlang } = rescue_each_var(Meta, Var, Guards),
@@ -114,7 +114,7 @@ rescue_guards(Meta, Var, Guards, S) ->
114114
[join(Meta, 'and', [IsTuple, IsException, OrElse])|Erlang]
115115
end,
116116
{
117-
{ 'when', Meta, [Var, reverse_join(Meta, 'when', Final)] },
117+
[{ 'when', Meta, [error, Var, reverse_join(Meta, 'when', Final)] }],
118118
Safe
119119
}.
120120

lib/elixir/test/elixir/kernel/quote_test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ defmodule Kernel.QuoteTest do
112112
assert fun.(1, 2, 3) == :ok
113113
end
114114

115+
test :when do
116+
assert {:->,_,[{[{:when,_,[1,2,3,4]}],_,5}]} = quote(do: (1, 2, 3 when 4 -> 5))
117+
assert {:->,_,[{[{:when,_,[1,2,3,4]}],_,5}]} = quote(do: ((1, 2, 3) when 4 -> 5))
118+
end
119+
115120
test :stab do
116121
assert { :->, _, [{[], _, _}] } = (quote do -> end)
117122
assert { :->, _, [{[], _, _}] } = (quote do: (->))

lib/elixir/test/elixir/macro_test.exs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,6 @@ defmodule MacroTest do
252252
end
253253

254254
test :fn_to_binary do
255-
assert Macro.to_string(quote do: (() -> x)) == "(() -> x)"
256255
assert Macro.to_string(quote do: (fn -> 1 + 2 end)) == "fn -> 1 + 2 end"
257256
assert Macro.to_string(quote do: (fn(x) -> x + 1 end)) == "fn x -> x + 1 end"
258257

@@ -274,6 +273,13 @@ defmodule MacroTest do
274273
"""
275274
end
276275

276+
test :when do
277+
assert Macro.to_string(quote do: (() -> x)) == "(() -> x)"
278+
assert Macro.to_string(quote do: (x when y -> z)) == "(x when y -> z)"
279+
assert Macro.to_string(quote do: (x, y when z -> w)) == "((x, y) when z -> w)"
280+
assert Macro.to_string(quote do: ((x, y) when z -> w)) == "((x, y) when z -> w)"
281+
end
282+
277283
test :partial_to_binary do
278284
assert Macro.to_string(quote do: identity(&1)) == "identity(&1)"
279285
end

0 commit comments

Comments
 (0)