Skip to content

Commit f4aed8e

Browse files
author
José Valim
committed
Reduce vars footprint in case/receive clauses
1 parent 008ee62 commit f4aed8e

File tree

3 files changed

+80
-95
lines changed

3 files changed

+80
-95
lines changed

lib/elixir/src/elixir_clauses.erl

Lines changed: 75 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -108,63 +108,58 @@ do_match(Meta, DecoupledClauses, S) ->
108108
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
109109
end,
110110

111-
{ TClauses, { TS, RawCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
111+
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
112112

113113
% Now get all the variables defined inside each clause
114-
CV = lists:reverse(RawCV),
115-
116-
AllVars = lists:umerge([orddict:fetch_keys(X) || X <- CV]),
117-
SharedVars = ordsets:intersection(CV),
118-
119-
case AllVars of
120-
[] -> { TClauses, TS };
121-
_ ->
122-
% Create a new scope that contains a list of all variables
123-
% defined inside all the clauses. It returns this new scope and
124-
% a list of tuples where the first element is the variable name,
125-
% the second one is the new pointer to the variable and the third
126-
% is the old pointer.
127-
{ FinalVars, FS } = lists:mapfoldl(fun(X, Acc) -> normalize_vars(X, SharedVars, Acc) end, TS, AllVars),
128-
129-
% Defines a tuple that will be used as left side of the match operator
130-
Line = ?line(Meta),
131-
LeftVars = [{var, Line, NewValue} || { _, _, NewValue, _ } <- FinalVars],
132-
133-
% Expand all clauses by adding a match operation at the end
134-
% that assigns variables missing in one clause to the others.
135-
expand_clauses(Meta, TClauses, CV, LeftVars, FinalVars, [], FS)
136-
end.
137-
138-
expand_clauses(Meta, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
139-
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) ||
140-
{ Var, Kind, _, OldValue } <- FinalVars],
141-
142-
AssignExpr = generate_match(Meta, LeftVars, RightVars),
143-
ClauseExprs = element(5, Clause),
144-
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
145-
146-
% If the last sentence has a match clause, we need to assign its value
147-
% in the variable list. If not, we insert the variable list before the
148-
% final clause in order to keep it tail call optimized.
149-
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
150-
true ->
151-
case Final of
152-
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
153-
{ [UserVar,AssignExpr,Final|RawClauseExprs], S };
154-
_ ->
155-
Line = ?line(Meta),
156-
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
157-
StorageExpr = { match, Line, StorageVar, Final },
158-
{ [StorageVar,AssignExpr,StorageExpr|RawClauseExprs], SS }
159-
end;
160-
false ->
161-
{ [Final,AssignExpr|RawClauseExprs], S }
162-
end,
163-
164-
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
165-
expand_clauses(Meta, T, V, LeftVars, FinalVars, [FinalClause|Acc], FS);
166-
167-
expand_clauses(_Meta, [], [], _LeftVars, _FinalVars, Acc, S) ->
114+
CV = lists:reverse(ReverseCV),
115+
AllVars = lists:foldl(fun(KV, Acc) ->
116+
orddict:merge(fun(_, _, V) -> V end, KV, Acc)
117+
end, orddict:new(), CV),
118+
119+
% Create a new scope that contains a list of all variables
120+
% defined inside all the clauses. It returns this new scope and
121+
% a list of tuples where the first element is the variable name,
122+
% the second one is the new pointer to the variable and the third
123+
% is the old pointer.
124+
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Ref }, Acc) ->
125+
normalize_vars(Key, Ref, Acc)
126+
end, TS, AllVars),
127+
128+
% Expand all clauses by adding a match operation at the end
129+
% that assigns variables missing in one clause to the others.
130+
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
131+
132+
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
133+
case generate_match_vars(FinalVars, ClauseVars, [], []) of
134+
{ [], [] } ->
135+
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
136+
{ Left, Right } ->
137+
MatchExpr = generate_match(Line, Left, Right),
138+
ClauseExprs = element(5, Clause),
139+
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
140+
141+
% If the last sentence has a match clause, we need to assign its value
142+
% in the variable list. If not, we insert the variable list before the
143+
% final clause in order to keep it tail call optimized.
144+
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
145+
true ->
146+
case Final of
147+
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
148+
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
149+
_ ->
150+
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
151+
StorageExpr = { match, Line, StorageVar, Final },
152+
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
153+
end;
154+
false ->
155+
{ [Final,MatchExpr|RawClauseExprs], S }
156+
end,
157+
158+
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
159+
expand_clauses(Line, T, V, FinalVars, [FinalClause|Acc], FS)
160+
end;
161+
162+
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
168163
{ lists:reverse(Acc), S }.
169164

170165
% Handle each key/value clause pair and translate them accordingly.
@@ -210,52 +205,44 @@ has_match_tuple(H) when is_list(H) ->
210205

211206
has_match_tuple(_) -> false.
212207

213-
% Normalize the given var checking its existence in the scope var dictionary.
214-
215-
normalize_vars({ Var, Kind } = Key, Shared, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
216-
{ NewValue, S1 } =
217-
case orddict:find(Key, Shared) of
218-
{ ok, SharedValue } ->
219-
{ SharedValue, S };
220-
error ->
221-
{ { _, _, ErlValue }, ErlS } = case (Kind /= nil) or (S#elixir_scope.noname) of
222-
true -> elixir_scope:build_erl_var(0, S);
223-
false -> elixir_scope:build_erl_var(0, Var, "_@" ++ atom_to_list(Var), S)
224-
end,
225-
{ ErlValue, ErlS }
226-
end,
227-
228-
S2 = S1#elixir_scope{
229-
vars=orddict:store(Key, NewValue, Vars),
230-
clause_vars=orddict:store(Key, NewValue, ClauseVars)
208+
% Normalize the given var in between clauses
209+
% by picking one value as reference and retriving
210+
% its previous value.
211+
212+
normalize_vars(Key, Value, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
213+
FS = S#elixir_scope{
214+
vars=orddict:store(Key, Value, Vars),
215+
clause_vars=orddict:store(Key, Value, ClauseVars)
231216
},
232217

233218
Expr = case orddict:find(Key, Vars) of
234219
{ ok, OldValue } -> { var, 0, OldValue };
235220
error -> { atom, 0, nil }
236221
end,
237222

238-
{ { Var, Kind, NewValue, Expr }, S2 }.
223+
{ { Key, Value, Expr }, FS }.
239224

240-
% Normalize a var by checking if it was defined in the clause.
241-
% If so, use it, otherwise use from main scope.
225+
% Generate match vars by checking if they were updated
226+
% or not and assigning the previous value.
242227

243-
normalize_clause_var(Var, Kind, OldValue, ClauseVars) ->
244-
case orddict:find({ Var, Kind }, ClauseVars) of
245-
{ ok, ClauseValue } -> { var, 0, ClauseValue };
246-
error -> OldValue
247-
end.
228+
generate_match_vars([{ Key, NewValue, OldValue }|T], ClauseVars, Left, Right) ->
229+
case orddict:find(Key, ClauseVars) of
230+
{ ok, NewValue } ->
231+
generate_match_vars(T, ClauseVars, Left, Right);
232+
{ ok, ClauseValue } ->
233+
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [{ var, 0, ClauseValue }|Right]);
234+
error ->
235+
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [OldValue|Right])
236+
end;
248237

249-
%% generate_match
238+
generate_match_vars([], _ClauseVars, Left, Right) ->
239+
{ Left, Right }.
250240

251-
generate_match(Meta, [Left], [Right]) ->
252-
{ match, ?line(Meta), Left, Right };
241+
generate_match(Line, [Left], [Right]) ->
242+
{ match, Line, Left, Right };
253243

254-
generate_match(Meta, LeftVars, RightVars) ->
255-
Line = ?line(Meta),
244+
generate_match(Line, LeftVars, RightVars) ->
256245
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
257246

258-
%% Listify
259-
260247
listify(Expr) when not is_list(Expr) -> [Expr];
261248
listify(Expr) -> Expr.

lib/elixir/src/elixir_errors.erl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
109109
io:format(file_format(Line, File, Message))
110110
end;
111111

112+
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
113+
[];
114+
112115
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
113116
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
114117
io:format(file_format(Line, File, Message));

lib/elixir/src/elixir_scope.erl

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
-module(elixir_scope).
44
-export([translate_var/5,
55
build_erl_var/2, build_ex_var/2,
6-
build_erl_var/3, build_ex_var/3,
7-
build_erl_var/4, build_ex_var/4,
86
serialize/1, deserialize/1,
97
serialize_with_vars/2, deserialize_with_vars/2,
108
to_erl_env/1, to_ex_env/1, filename/1,
@@ -30,8 +28,8 @@ translate_var(Meta, Name, Kind, S, Callback) ->
3028
{ Else, _ } ->
3129
{ NewVar, NS } = if
3230
Kind /= nil -> build_erl_var(Line, S);
33-
Else -> build_erl_var(Line, Name, S);
34-
S#elixir_scope.noname -> build_erl_var(Line, Name, S);
31+
Else -> build_erl_var(Line, Name, Name, S);
32+
S#elixir_scope.noname -> build_erl_var(Line, Name, Name, S);
3533
true -> { { var, Line, Name }, S }
3634
end,
3735
RealName = element(3, NewVar),
@@ -58,9 +56,6 @@ translate_var(Meta, Name, Kind, S, Callback) ->
5856
build_ex_var(Line, S) -> build_ex_var(Line, '', "_", S).
5957
build_erl_var(Line, S) -> build_erl_var(Line, '', "_", S).
6058

61-
build_ex_var(Line, Key, S) -> build_ex_var(Line, Key, Key, S).
62-
build_erl_var(Line, Key, S) -> build_erl_var(Line, Key, Key, S).
63-
6459
build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
6560
New = orddict:update_counter(Key, 1, Counter),
6661
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.

0 commit comments

Comments
 (0)