Skip to content

Commit f95cafe

Browse files
author
José Valim
committed
Fix regression when case is used inside cond head
1 parent f0e1443 commit f95cafe

File tree

8 files changed

+86
-66
lines changed

8 files changed

+86
-66
lines changed

lib/elixir/src/elixir_clauses.erl

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ do_clauses(Meta, DecoupledClauses, Return, S) ->
7979
% Transform tree just passing the variables counter forward
8080
% and storing variables defined inside each clause.
8181
Transformer = fun(X, {SAcc, VAcc}) ->
82-
{TX, TS} = each_clause(X, Return, SAcc),
82+
{TX, TS} = each_clause(Meta, X, Return, SAcc),
8383
{TX, {elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
8484
end,
8585

@@ -139,15 +139,27 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
139139

140140
% Handle each key/value clause pair and translate them accordingly.
141141

142-
each_clause({match, Meta, [Condition], Expr}, Return, S) ->
142+
each_clause(Export, {match, Meta, [Condition], Expr}, Return, S) ->
143+
Fun = wrap_export_fun(Export, fun elixir_translator:translate_args/2),
143144
{Arg, Guards} = extract_guards(Condition),
144-
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
145+
clause(?line(Meta), Fun, [Arg], Expr, Guards, Return, S);
145146

146-
each_clause({expr, Meta, [Condition], Expr}, Return, S) ->
147-
{TCondition, SC} = elixir_translator:translate(Condition, S),
147+
each_clause(Export, {expr, Meta, [Condition], Expr}, Return, S) ->
148+
{TCondition, SC} = (wrap_export_fun(Export, fun elixir_translator:translate/2))(Condition, S),
148149
{TExpr, SB} = elixir_translator:translate_block(Expr, Return, SC),
149150
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
150151

152+
wrap_export_fun(Meta, Fun) ->
153+
case lists:keyfind(export_head, 1, Meta) of
154+
{export_head, true} ->
155+
Fun;
156+
_ ->
157+
fun(Args, S) ->
158+
{TArgs, TS} = Fun(Args, S),
159+
{TArgs, TS#elixir_scope{export_vars = S#elixir_scope.export_vars}}
160+
end
161+
end.
162+
151163
% Check if the given expression is a match tuple.
152164
% This is a small optimization to allow us to change
153165
% existing assignments instead of creating new ones every time.
@@ -176,7 +188,7 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S)
176188
VS = S#elixir_scope{
177189
vars=orddict:store(Key, Value, Vars),
178190
export_vars=orddict:store(Key, Value, ClauseVars)
179-
},
191+
},
180192

181193
Expr = case orddict:find(Key, Vars) of
182194
{ok, {PreValue, _}} -> {var, 0, PreValue};

lib/elixir/src/elixir_def.erl

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -168,16 +168,8 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
168168

169169
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
170170

171-
%% Macros receive a special argument on invocation. Notice it does
172-
%% not affect the arity of the stored function, but the clause
173-
%% already contains it.
174-
EAllArgs = case is_macro(Kind) of
175-
true -> [{'_@CALLER', [{line,Line}], nil}|EArgs];
176-
false -> EArgs
177-
end,
178-
179171
S = elixir_env:env_to_scope(E),
180-
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
172+
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
181173
{Clauses, Super} = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
182174

183175
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
@@ -192,22 +184,30 @@ translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
192184
{TClause, TS} = elixir_clauses:clause(Line,
193185
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
194186

195-
%% Set __CALLER__ if used
196-
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
197-
true ->
198-
FBody = {'match', Line,
199-
{'var', Line, '__CALLER__'},
200-
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
201-
},
202-
setelement(5, TClause, [FBody|element(5, TClause)]);
203-
false -> TClause
187+
FClause = case is_macro(Kind) of
188+
true ->
189+
FArgs = {var, Line, '_@CALLER'},
190+
MClause = setelement(3, TClause, [FArgs|element(3, TClause)]),
191+
192+
case TS#elixir_scope.caller of
193+
true ->
194+
FBody = {'match', Line,
195+
{'var', Line, '__CALLER__'},
196+
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
197+
},
198+
setelement(5, MClause, [FBody|element(5, TClause)]);
199+
false ->
200+
MClause
201+
end;
202+
false ->
203+
TClause
204204
end,
205205

206206
{[FClause], TS#elixir_scope.super}.
207207

208-
expr_from_body(_Line, nil) -> nil;
208+
expr_from_body(_Line, nil) -> nil;
209209
expr_from_body(_Line, [{do, Expr}]) -> Expr;
210-
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
210+
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
211211

212212
is_macro(defmacro) -> true;
213213
is_macro(defmacrop) -> true;

lib/elixir/src/elixir_def_defaults.erl

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,19 @@ unpack(Kind, Name, Args, S) ->
2121
%% Unpack default from given args.
2222
%% Returns the given arguments without their default
2323
%% clauses and a list of clauses for the default calls.
24-
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
25-
Base = build_match(Acc, Line, []),
26-
{Args, Invoke} = extract_defaults(List, Line, length(Base), [], []),
24+
unpack_each(Kind, Name, [{'\\\\', DefMeta, [Expr, _]}|T] = List, Acc, Clauses, S) ->
25+
Base = wrap_kind(Kind, build_match(Acc, [])),
26+
{Args, Invoke} = extract_defaults(List, length(Base), [], []),
2727

2828
{DefArgs, SA} = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
2929
{DefInvoke, _} = elixir_translator:translate_args(Base ++ Invoke, SA),
3030

31+
Line = ?line(DefMeta),
32+
3133
Call = {call, Line,
3234
{atom, Line, name_for_kind(Kind, Name)},
3335
DefInvoke
34-
},
36+
},
3537

3638
Clause = {clause, Line, DefArgs, [], [Call]},
3739
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
@@ -44,25 +46,28 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
4446

4547
% Extract default values from args following the current default clause.
4648

47-
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
48-
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
49+
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Counter, NewArgs, NewInvoke) ->
50+
extract_defaults(T, Counter, NewArgs, [Default|NewInvoke]);
4951

50-
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
51-
H = {elixir_utils:atom_concat(["_@D", Counter]), Line, nil},
52-
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
52+
extract_defaults([_|T], Counter, NewArgs, NewInvoke) ->
53+
H = {elixir_utils:atom_concat(["x", Counter]), [], nil},
54+
extract_defaults(T, Counter + 1, [H|NewArgs], [H|NewInvoke]);
5355

54-
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
56+
extract_defaults([], _Counter, NewArgs, NewInvoke) ->
5557
{lists:reverse(NewArgs), lists:reverse(NewInvoke)}.
5658

5759
% Build matches for all the previous argument until the current default clause.
5860

59-
build_match([], _Line, Acc) -> Acc;
61+
build_match([], Acc) -> Acc;
6062

61-
build_match([_|T], Line, Acc) ->
62-
Var = {elixir_utils:atom_concat(["_@D", length(T)]), Line, nil},
63-
build_match(T, Line, [Var|Acc]).
63+
build_match([_|T], Acc) ->
64+
Var = {elixir_utils:atom_concat(["x", length(T)]), [], nil},
65+
build_match(T, [Var|Acc]).
6466

6567
% Given the invoked function name based on the kind
6668

69+
wrap_kind(Kind, Args) when Kind == defmacro; Kind == defmacrop -> [{c, [], nil}|Args];
70+
wrap_kind(_Kind, Args) -> Args.
71+
6772
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> elixir_utils:macro_name(Name);
6873
name_for_kind(_Kind, Name) -> Name.

lib/elixir/src/elixir_exp.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ expand({'_', _, Kind} = Var, E) when is_atom(Kind) ->
283283
expand({Name, Meta, Kind} = Var, #{context := match, export_vars := Export} = E) when is_atom(Name), is_atom(Kind) ->
284284
Pair = {Name, var_kind(Meta, Kind)},
285285
NewVars = ordsets:add_element(Pair, ?m(E, vars)),
286-
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= {export, false}) of
286+
NewExport = case (Export /= nil) of
287287
true -> ordsets:add_element(Pair, Export);
288288
false -> Export
289289
end,

lib/elixir/src/elixir_exp_clauses.erl

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -171,17 +171,6 @@ normalize_rescue(Other) ->
171171

172172
%% Expansion helpers
173173

174-
export_vars({Left, Meta, Right}) when is_atom(Left), is_list(Meta), is_atom(Right) ->
175-
{Left, [{export,false}|Meta], Right};
176-
export_vars({Left, Meta, Right}) ->
177-
{export_vars(Left), Meta, export_vars(Right)};
178-
export_vars({Left, Right}) ->
179-
{export_vars(Left), export_vars(Right)};
180-
export_vars(List) when is_list(List) ->
181-
[export_vars(X) || X <- List];
182-
export_vars(Other) ->
183-
Other.
184-
185174
%% Returns a function that expands arguments
186175
%% considering we have at maximum one entry.
187176
expand_one(Meta, Kind, Key, Fun) ->
@@ -197,8 +186,13 @@ expand_one(Meta, Kind, Key, Fun) ->
197186
expand_with_export(Meta, Kind, Fun, {Key, Clauses}, Acc, E) when is_list(Clauses) ->
198187
EFun =
199188
case lists:keyfind(export_head, 1, Meta) of
200-
{export_head, true} -> Fun;
201-
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
189+
{export_head, true} ->
190+
Fun;
191+
_ ->
192+
fun(Args, #{export_vars := ExportVars} = EE) ->
193+
{FArgs, FE} = Fun(Args, EE),
194+
{FArgs, FE#{export_vars := ExportVars}}
195+
end
202196
end,
203197
Transformer = fun(Clause, Vars) ->
204198
{EClause, EC} = clause(Meta, Kind, EFun, Clause, E),

lib/elixir/src/elixir_scope.erl

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,27 @@ translate_var(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
2727
true ->
2828
{{var, Line, Current}, S};
2929
false ->
30-
%% If the variable is not exported, we use a counter name.
31-
%% The same if the variable already exists or we are in a
32-
%% noname context.
33-
Private = (lists:keyfind(export, 1, Meta) == {export, false}),
34-
30+
%% We attempt to give vars a nice name because we
31+
%% still use the unused vars warnings from erl_lint.
32+
%%
33+
%% Once we move the warning to Elixir compiler, we
34+
%% can name vars as _@COUNTER.
3535
{NewVar, Counter, NS} =
3636
if
3737
Kind /= nil ->
3838
build_var('_', S);
39-
Private orelse Exists orelse S#elixir_scope.noname ->
40-
build_var(Name, S);
4139
true ->
42-
{Name, 0, S}
40+
build_var(Name, S)
4341
end,
4442

4543
FS = NS#elixir_scope{
4644
vars=orddict:store(Tuple, {NewVar, Counter}, Vars),
4745
match_vars=ordsets:add_element(Tuple, MatchVars),
4846
export_vars=case S#elixir_scope.export_vars of
49-
EV when Private; EV == nil -> EV;
50-
EV -> orddict:store(Tuple, {NewVar, Counter}, EV)
47+
nil -> nil;
48+
EV -> orddict:store(Tuple, {NewVar, Counter}, EV)
5149
end
52-
},
50+
},
5351

5452
{{var, Line, NewVar}, FS}
5553
end;

lib/elixir/src/elixir_translator.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ build_truthy_clause(Meta, Condition, Body) ->
392392
true ->
393393
{{'->', Meta, [[true], Body]}, false};
394394
false ->
395-
Var = {'cond', [{export, false}], 'Elixir'},
395+
Var = {'cond', [], 'Elixir'},
396396
Head = {'when', [], [Var,
397397
{'__op__', [], [
398398
'andalso',

lib/elixir/test/elixir/kernel/case_test.exs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ defmodule Kernel.CaseTest do
3737
refute 1.0 in [1, 2, 3], "not in assertion"
3838
end
3939

40+
test :in_cond_clause do
41+
assert (cond do
42+
format() && (f = format()) ->
43+
f
44+
true ->
45+
:text
46+
end) == :html
47+
end
48+
49+
defp format, do: :html
50+
4051
defp vars_case(x, vx) do
4152
case x > 400 do
4253
true ->

0 commit comments

Comments
 (0)