File 2411-Traverse-eval-forms-for-function-used-vars-once.patch of Package erlang
From 20d15bd115b34d814d2fa3bae5509b35f5ae9664 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 7 Dec 2021 15:51:51 +0100
Subject: [PATCH] Traverse eval forms for function used vars once
Prior to this implementation, every time an
anonymous function was defined via eval, we
would traverse its tree to find its used vars.
However, if there are nested anonymous functions,
they are traversed once by their parent and then
traversed again each time they are executed.
This means any form of nested loops are extremely
expensive in the shell.
With this patch, we traverse the tree once and
then we keep the tree of used vars for all of its
anonymous functions inside. This drastically speeds
up those scenarios.
Take this Elixir code:
lines
|> Enum.reduce(%{}, fn
[x, y1, x, y2], grid ->
Enum.reduce(y1..y2, grid, fn y, grid ->
Map.update(grid, {x, y}, 1, &(&1 + 1))
end)
[x1, y, x2, y], grid ->
Enum.reduce(x1..x2, grid, fn x, grid ->
Map.update(grid, {x, y}, 1, &(&1 + 1))
end)
[x1, y1, x2, y2], grid ->
Enum.reduce(Enum.zip(x1..x2, y1..y2), grid, fn point, grid ->
Map.update(grid, point, 1, &(&1 + 1))
end)
end)
|> Enum.count(fn {_, v} -> v > 1 end)
In the code above, there are three levels of anonymous function,
which then become very expensive to traverse on every iteration.
With this patch, the time to evaluate the code above goes from
2s to 0.6s for a large input.
---
lib/stdlib/src/erl_eval.erl | 480 ++++++++++++++++-------------
lib/stdlib/src/erl_lint.erl | 25 +-
lib/stdlib/test/erl_eval_SUITE.erl | 40 +--
3 files changed, 306 insertions(+), 239 deletions(-)
diff --git a/lib/stdlib/src/erl_eval.erl b/lib/stdlib/src/erl_eval.erl
index abb6d6813b..135d2713e7 100644
--- a/lib/stdlib/src/erl_eval.erl
+++ b/lib/stdlib/src/erl_eval.erl
@@ -69,6 +69,8 @@
-define(STACKTRACE,
element(2, erlang:process_info(self(), current_stacktrace))).
+empty_fun_used_vars() -> #{}.
+
%% exprs(ExpressionSeq, Bindings)
%% exprs(ExpressionSeq, Bindings, LocalFuncHandler)
%% exprs(ExpressionSeq, Bindings, LocalFuncHandler, ExternalFuncHandler)
@@ -87,8 +89,8 @@
NewBindings :: binding_struct()).
exprs(Exprs, Bs) ->
case check_command(Exprs, Bs) of
- ok ->
- exprs(Exprs, Bs, none, none, none);
+ ok ->
+ exprs(Exprs, Bs, none, none, none, empty_fun_used_vars());
{error,{_Location,_Mod,Error}} ->
erlang:raise(error, Error, ?STACKTRACE)
end.
@@ -101,7 +103,7 @@ exprs(Exprs, Bs) ->
Value :: value(),
NewBindings :: binding_struct()).
exprs(Exprs, Bs, Lf) ->
- exprs(Exprs, Bs, Lf, none, none).
+ exprs(Exprs, Bs, Lf, none, none, empty_fun_used_vars()).
-spec(exprs(Expressions, Bindings, LocalFunctionHandler,
NonLocalFunctionHandler) ->
@@ -113,14 +115,25 @@ exprs(Exprs, Bs, Lf) ->
Value :: value(),
NewBindings :: binding_struct()).
exprs(Exprs, Bs, Lf, Ef) ->
- exprs(Exprs, Bs, Lf, Ef, none).
+ exprs(Exprs, Bs, Lf, Ef, none, empty_fun_used_vars()).
-exprs([E], Bs0, Lf, Ef, RBs) ->
- expr(E, Bs0, Lf, Ef, RBs);
-exprs([E|Es], Bs0, Lf, Ef, RBs) ->
+-spec(exprs(Expressions, Bindings, LocalFunctionHandler,
+ NonLocalFunctionHandler, ReturnFormat, FunUsedVars) ->
+ {value, Value, NewBindings} when
+ Expressions :: expressions(),
+ Bindings :: binding_struct(),
+ LocalFunctionHandler :: local_function_handler(),
+ NonLocalFunctionHandler :: non_local_function_handler(),
+ ReturnFormat :: none | value,
+ FunUsedVars :: erl_lint:fun_used_vars(),
+ Value :: value(),
+ NewBindings :: binding_struct()).
+exprs([E], Bs0, Lf, Ef, RBs, FUVs) ->
+ expr(E, Bs0, Lf, Ef, RBs, FUVs);
+exprs([E|Es], Bs0, Lf, Ef, RBs, FUVs) ->
RBs1 = none,
- {value,_V,Bs} = expr(E, Bs0, Lf, Ef, RBs1),
- exprs(Es, Bs, Lf, Ef, RBs).
+ {value,_V,Bs} = expr(E, Bs0, Lf, Ef, RBs1, FUVs),
+ exprs(Es, Bs, Lf, Ef, RBs, FUVs).
%% expr(Expression, Bindings)
%% expr(Expression, Bindings, LocalFuncHandler)
@@ -138,7 +151,7 @@ exprs([E|Es], Bs0, Lf, Ef, RBs) ->
NewBindings :: binding_struct()).
expr(E, Bs) ->
case check_command([E], Bs) of
- ok ->
+ ok ->
expr(E, Bs, none, none, none);
{error,{_Location,_Mod,Error}} ->
erlang:raise(error, Error, ?STACKTRACE)
@@ -184,9 +197,9 @@ fun_data(F) when is_function(F) ->
case erlang:fun_info(F, module) of
{module,?MODULE} ->
case erlang:fun_info(F, env) of
- {env,[{FBs,_FLf,_FEf,FCs}]} ->
+ {env,[{FBs,_FLf,_FEf,_FUVs,FCs}]} ->
{fun_data,FBs,FCs};
- {env,[{FBs,_FLf,_FEf,FCs,FName}]} ->
+ {env,[{FBs,_FLf,_FEf,_FUVs,FCs,FName}]} ->
{named_fun_data,FBs,FName,FCs}
end;
_ ->
@@ -205,49 +218,63 @@ fun_data(_T) ->
ReturnFormat :: none | value,
Value :: value(),
NewBindings :: binding_struct()).
-expr({var,_,V}, Bs, _Lf, _Ef, RBs) ->
+expr(Expr, Bs, Lf, Ef, Rbs) ->
+ expr(Expr, Bs, Lf, Ef, Rbs, empty_fun_used_vars()).
+
+-spec(expr(Expression, Bindings, LocalFunctionHandler,
+ NonLocalFunctionHandler, ReturnFormat, FunUsedVars) ->
+ {value, Value, NewBindings} | Value when
+ Expression :: expression(),
+ Bindings :: binding_struct(),
+ LocalFunctionHandler :: local_function_handler(),
+ NonLocalFunctionHandler :: non_local_function_handler(),
+ ReturnFormat :: none | value,
+ FunUsedVars :: erl_lint:fun_used_vars(),
+ Value :: value(),
+ NewBindings :: binding_struct()).
+expr({var,_,V}, Bs, _Lf, _Ef, RBs, _FUVs) ->
case binding(V, Bs) of
{value,Val} ->
ret_expr(Val, Bs, RBs);
unbound -> % Cannot not happen if checked by erl_lint
erlang:raise(error, {unbound,V}, ?STACKTRACE)
end;
-expr({char,_,C}, Bs, _Lf, _Ef, RBs) ->
+expr({char,_,C}, Bs, _Lf, _Ef, RBs, _FUVs) ->
ret_expr(C, Bs, RBs);
-expr({integer,_,I}, Bs, _Lf, _Ef, RBs) ->
+expr({integer,_,I}, Bs, _Lf, _Ef, RBs, _FUVs) ->
ret_expr(I, Bs, RBs);
-expr({float,_,F}, Bs, _Lf, _Ef, RBs) ->
+expr({float,_,F}, Bs, _Lf, _Ef, RBs, _FUVs) ->
ret_expr(F, Bs, RBs);
-expr({atom,_,A}, Bs, _Lf, _Ef, RBs) ->
+expr({atom,_,A}, Bs, _Lf, _Ef, RBs, _FUVs) ->
ret_expr(A, Bs, RBs);
-expr({string,_,S}, Bs, _Lf, _Ef, RBs) ->
+expr({string,_,S}, Bs, _Lf, _Ef, RBs, _FUVs) ->
ret_expr(S, Bs, RBs);
-expr({nil, _}, Bs, _Lf, _Ef, RBs) ->
+expr({nil, _}, Bs, _Lf, _Ef, RBs, _FUVs) ->
ret_expr([], Bs, RBs);
-expr({cons,_,H0,T0}, Bs0, Lf, Ef, RBs) ->
- {value,H,Bs1} = expr(H0, Bs0, Lf, Ef, none),
- {value,T,Bs2} = expr(T0, Bs0, Lf, Ef, none),
+expr({cons,_,H0,T0}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {value,H,Bs1} = expr(H0, Bs0, Lf, Ef, none, FUVs),
+ {value,T,Bs2} = expr(T0, Bs0, Lf, Ef, none, FUVs),
ret_expr([H|T], merge_bindings(Bs1, Bs2), RBs);
-expr({lc,_,E,Qs}, Bs, Lf, Ef, RBs) ->
- eval_lc(E, Qs, Bs, Lf, Ef, RBs);
-expr({bc,_,E,Qs}, Bs, Lf, Ef, RBs) ->
- eval_bc(E, Qs, Bs, Lf, Ef, RBs);
-expr({tuple,_,Es}, Bs0, Lf, Ef, RBs) ->
- {Vs,Bs} = expr_list(Es, Bs0, Lf, Ef),
+expr({lc,_,E,Qs}, Bs, Lf, Ef, RBs, FUVs) ->
+ eval_lc(E, Qs, Bs, Lf, Ef, RBs, FUVs);
+expr({bc,_,E,Qs}, Bs, Lf, Ef, RBs, FUVs) ->
+ eval_bc(E, Qs, Bs, Lf, Ef, RBs, FUVs);
+expr({tuple,_,Es}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {Vs,Bs} = expr_list(Es, Bs0, Lf, Ef, FUVs),
ret_expr(list_to_tuple(Vs), Bs, RBs);
-expr({record_field,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs) ->
+expr({record_field,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs, _FUVs) ->
erlang:raise(error, {undef_record,Name}, ?STACKTRACE);
-expr({record_index,_,Name,_}, _Bs, _Lf, _Ef, _RBs) ->
+expr({record_index,_,Name,_}, _Bs, _Lf, _Ef, _RBs, _FUVs) ->
erlang:raise(error, {undef_record,Name}, ?STACKTRACE);
-expr({record,_,Name,_}, _Bs, _Lf, _Ef, _RBs) ->
+expr({record,_,Name,_}, _Bs, _Lf, _Ef, _RBs, _FUVs) ->
erlang:raise(error, {undef_record,Name}, ?STACKTRACE);
-expr({record,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs) ->
+expr({record,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs, _FUVs) ->
erlang:raise(error, {undef_record,Name}, ?STACKTRACE);
%% map
-expr({map,_,Binding,Es}, Bs0, Lf, Ef, RBs) ->
- {value, Map0, Bs1} = expr(Binding, Bs0, Lf, Ef, none),
- {Vs,Bs2} = eval_map_fields(Es, Bs0, Lf, Ef),
+expr({map,_,Binding,Es}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {value, Map0, Bs1} = expr(Binding, Bs0, Lf, Ef, none, FUVs),
+ {Vs,Bs2} = eval_map_fields(Es, Bs0, Lf, Ef, FUVs),
_ = maps:put(k, v, Map0), %Validate map.
Map1 = lists:foldl(fun ({map_assoc,K,V}, Mi) ->
maps:put(K, V, Mi);
@@ -255,39 +282,39 @@ expr({map,_,Binding,Es}, Bs0, Lf, Ef, RBs) ->
maps:update(K, V, Mi)
end, Map0, Vs),
ret_expr(Map1, merge_bindings(Bs2, Bs1), RBs);
-expr({map,_,Es}, Bs0, Lf, Ef, RBs) ->
- {Vs,Bs} = eval_map_fields(Es, Bs0, Lf, Ef),
+expr({map,_,Es}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {Vs,Bs} = eval_map_fields(Es, Bs0, Lf, Ef, FUVs),
ret_expr(lists:foldl(fun
({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi)
end, maps:new(), Vs), Bs, RBs);
-expr({block,_,Es}, Bs, Lf, Ef, RBs) ->
- exprs(Es, Bs, Lf, Ef, RBs);
-expr({'if',_,Cs}, Bs, Lf, Ef, RBs) ->
- if_clauses(Cs, Bs, Lf, Ef, RBs);
-expr({'case',_,E,Cs}, Bs0, Lf, Ef, RBs) ->
- {value,Val,Bs} = expr(E, Bs0, Lf, Ef, none),
- case_clauses(Val, Cs, Bs, Lf, Ef, RBs);
-expr({'try',_,B,Cases,Catches,AB}, Bs, Lf, Ef, RBs) ->
- try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs);
-expr({'receive',_,Cs}, Bs, Lf, Ef, RBs) ->
- receive_clauses(Cs, Bs, Lf, Ef, RBs);
-expr({'receive',_, Cs, E, TB}, Bs0, Lf, Ef, RBs) ->
- {value,T,Bs} = expr(E, Bs0, Lf, Ef, none),
- receive_clauses(T, Cs, {TB,Bs}, Bs0, Lf, Ef, RBs);
-expr({'fun',_Anno,{function,Mod0,Name0,Arity0}}, Bs0, Lf, Ef, RBs) ->
- {[Mod,Name,Arity],Bs} = expr_list([Mod0,Name0,Arity0], Bs0, Lf, Ef),
+expr({block,_,Es}, Bs, Lf, Ef, RBs, FUVs) ->
+ exprs(Es, Bs, Lf, Ef, RBs, FUVs);
+expr({'if',_,Cs}, Bs, Lf, Ef, RBs, FUVs) ->
+ if_clauses(Cs, Bs, Lf, Ef, RBs, FUVs);
+expr({'case',_,E,Cs}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {value,Val,Bs} = expr(E, Bs0, Lf, Ef, none, FUVs),
+ case_clauses(Val, Cs, Bs, Lf, Ef, RBs, FUVs);
+expr({'try',_,B,Cases,Catches,AB}, Bs, Lf, Ef, RBs, FUVs) ->
+ try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs, FUVs);
+expr({'receive',_,Cs}, Bs, Lf, Ef, RBs, FUVs) ->
+ receive_clauses(Cs, Bs, Lf, Ef, RBs, FUVs);
+expr({'receive',_, Cs, E, TB}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {value,T,Bs} = expr(E, Bs0, Lf, Ef, none, FUVs),
+ receive_clauses(T, Cs, {TB,Bs}, Bs0, Lf, Ef, RBs, FUVs);
+expr({'fun',_Anno,{function,Mod0,Name0,Arity0}}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {[Mod,Name,Arity],Bs} = expr_list([Mod0,Name0,Arity0], Bs0, Lf, Ef, FUVs),
F = erlang:make_fun(Mod, Name, Arity),
- ret_expr(F, Bs, RBs);
-expr({'fun',_Anno,{function,Name,Arity}}, _Bs0, _Lf, _Ef, _RBs) -> % R8
+ ret_expr(F, Bs, RBs);
+expr({'fun',_Anno,{function,Name,Arity}}, _Bs0, _Lf, _Ef, _RBs, _FUVs) -> % R8
%% Don't know what to do...
erlang:raise(error, undef, [{?MODULE,Name,Arity}|?STACKTRACE]);
-expr({'fun',Anno,{clauses,Cs}} = Ex, Bs, Lf, Ef, RBs) ->
- {ok,Used} = erl_lint:used_vars([Ex], bindings(Bs)),
- En = filter_bindings(fun(K,_V) -> member(K,Used) end, Bs),
- Info = {En,Lf,Ef,Cs},
+expr({'fun',Anno,{clauses,Cs}} = Ex, Bs, Lf, Ef, RBs, FUVs) ->
+ {En,NewFUVs} = fun_used_bindings(Ex, Cs, Bs, FUVs),
+ Info = {En,Lf,Ef,NewFUVs,Cs},
+
%% This is a really ugly hack!
- F =
+ F =
case length(element(3,hd(Cs))) of
0 -> fun () -> eval_fun([], Info) end;
1 -> fun (A) -> eval_fun([A], Info) end;
@@ -327,10 +354,10 @@ expr({'fun',Anno,{clauses,Cs}} = Ex, Bs, Lf, Ef, RBs) ->
?STACKTRACE)
end,
ret_expr(F, Bs, RBs);
-expr({named_fun,Anno,Name,Cs} = Ex, Bs, Lf, Ef, RBs) ->
- {ok,Used} = erl_lint:used_vars([Ex], bindings(Bs)),
- En = filter_bindings(fun(K,_V) -> member(K,Used) end, Bs),
- Info = {En,Lf,Ef,Cs,Name},
+expr({named_fun,Anno,Name,Cs} = Ex, Bs, Lf, Ef, RBs, FUVs) ->
+ {En,NewFUVs} = fun_used_bindings(Ex, Cs, Bs, FUVs),
+ Info = {En,Lf,Ef,NewFUVs,Cs,Name},
+
%% This is a really ugly hack!
F =
case length(element(3,hd(Cs))) of
@@ -378,27 +405,27 @@ expr({named_fun,Anno,Name,Cs} = Ex, Bs, Lf, Ef, RBs) ->
?STACKTRACE)
end,
ret_expr(F, Bs, RBs);
-expr({call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[{lc,_,_E,_Qs}=LC | As0]},
- Bs0, Lf, Ef, RBs) when length(As0) =< 1 ->
+expr({call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[{lc,_,_E,_Qs}=LC | As0]},
+ Bs0, Lf, Ef, RBs, FUVs) when length(As0) =< 1 ->
%% No expansion or evaluation of module name or function name.
MaxLine = find_maxline(LC),
{LC1, D} = hide_calls(LC, MaxLine),
case qlc:transform_from_evaluator(LC1, Bs0) of
{ok,{call,A,Remote,[QLC]}} ->
QLC1 = unhide_calls(QLC, MaxLine, D),
- expr({call,A,Remote,[QLC1 | As0]}, Bs0, Lf, Ef, RBs);
+ expr({call,A,Remote,[QLC1 | As0]}, Bs0, Lf, Ef, RBs, FUVs);
{not_ok,Error} ->
ret_expr(Error, Bs0, RBs)
end;
expr({call,A1,{remote,A2,{record_field,_,{atom,_,''},{atom,_,qlc}=Mod},
{atom,_,q}=Func},
- [{lc,_,_E,_Qs} | As0]=As},
- Bs, Lf, Ef, RBs) when length(As0) =< 1 ->
- expr({call,A1,{remote,A2,Mod,Func},As}, Bs, Lf, Ef, RBs);
-expr({call,_,{remote,_,Mod,Func},As0}, Bs0, Lf, Ef, RBs) ->
- {value,M,Bs1} = expr(Mod, Bs0, Lf, Ef, none),
- {value,F,Bs2} = expr(Func, Bs0, Lf, Ef, none),
- {As,Bs3} = expr_list(As0, merge_bindings(Bs1, Bs2), Lf, Ef),
+ [{lc,_,_E,_Qs} | As0]=As},
+ Bs, Lf, Ef, RBs, FUVs) when length(As0) =< 1 ->
+ expr({call,A1,{remote,A2,Mod,Func},As}, Bs, Lf, Ef, RBs, FUVs);
+expr({call,_,{remote,_,Mod,Func},As0}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {value,M,Bs1} = expr(Mod, Bs0, Lf, Ef, none, FUVs),
+ {value,F,Bs2} = expr(Func, Bs0, Lf, Ef, none, FUVs),
+ {As,Bs3} = expr_list(As0, merge_bindings(Bs1, Bs2), Lf, Ef, FUVs),
%% M could be a parameterized module (not an atom).
case is_atom(M) andalso erl_internal:bif(M, F, length(As)) of
true ->
@@ -406,25 +433,25 @@ expr({call,_,{remote,_,Mod,Func},As0}, Bs0, Lf, Ef, RBs) ->
false ->
do_apply(M, F, As, Bs3, Ef, RBs)
end;
-expr({call,_,{atom,_,Func},As0}, Bs0, Lf, Ef, RBs) ->
+expr({call,_,{atom,_,Func},As0}, Bs0, Lf, Ef, RBs, FUVs) ->
case erl_internal:bif(Func, length(As0)) of
true ->
{As,Bs} = expr_list(As0, Bs0, Lf, Ef),
bif(Func, As, Bs, Ef, RBs);
false ->
- local_func(Func, As0, Bs0, Lf, Ef, RBs)
+ local_func(Func, As0, Bs0, Lf, Ef, RBs, FUVs)
end;
-expr({call,_,Func0,As0}, Bs0, Lf, Ef, RBs) -> % function or {Mod,Fun}
- {value,Func,Bs1} = expr(Func0, Bs0, Lf, Ef, none),
- {As,Bs2} = expr_list(As0, Bs1, Lf, Ef),
+expr({call,_,Func0,As0}, Bs0, Lf, Ef, RBs, FUVs) -> % function or {Mod,Fun}
+ {value,Func,Bs1} = expr(Func0, Bs0, Lf, Ef, none, FUVs),
+ {As,Bs2} = expr_list(As0, Bs1, Lf, Ef, FUVs),
case Func of
{M,F} when is_atom(M), is_atom(F) ->
erlang:raise(error, {badfun,Func}, ?STACKTRACE);
_ ->
do_apply(Func, As, Bs2, Ef, RBs)
end;
-expr({'catch',_,Expr}, Bs0, Lf, Ef, RBs) ->
- try expr(Expr, Bs0, Lf, Ef, none) of
+expr({'catch',_,Expr}, Bs0, Lf, Ef, RBs, FUVs) ->
+ try expr(Expr, Bs0, Lf, Ef, none, FUVs) of
{value,V,Bs} ->
ret_expr(V, Bs, RBs)
catch
@@ -435,47 +462,47 @@ expr({'catch',_,Expr}, Bs0, Lf, Ef, RBs) ->
error:Reason:Stacktrace ->
ret_expr({'EXIT',{Reason,Stacktrace}}, Bs0, RBs)
end;
-expr({match,_,Lhs,Rhs0}, Bs0, Lf, Ef, RBs) ->
- {value,Rhs,Bs1} = expr(Rhs0, Bs0, Lf, Ef, none),
+expr({match,_,Lhs,Rhs0}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {value,Rhs,Bs1} = expr(Rhs0, Bs0, Lf, Ef, none, FUVs),
case match(Lhs, Rhs, Bs1) of
{match,Bs} ->
ret_expr(Rhs, Bs, RBs);
nomatch -> erlang:raise(error, {badmatch,Rhs}, ?STACKTRACE)
end;
-expr({op,_,Op,A0}, Bs0, Lf, Ef, RBs) ->
- {value,A,Bs} = expr(A0, Bs0, Lf, Ef, none),
+expr({op,_,Op,A0}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {value,A,Bs} = expr(A0, Bs0, Lf, Ef, none, FUVs),
eval_op(Op, A, Bs, Ef, RBs);
-expr({op,_,'andalso',L0,R0}, Bs0, Lf, Ef, RBs) ->
- {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none),
+expr({op,_,'andalso',L0,R0}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
V = case L of
true ->
- {value,R,_} = expr(R0, Bs1, Lf, Ef, none),
+ {value,R,_} = expr(R0, Bs1, Lf, Ef, none, FUVs),
R;
false -> false;
_ -> erlang:raise(error, {badarg,L}, ?STACKTRACE)
end,
ret_expr(V, Bs1, RBs);
-expr({op,_,'orelse',L0,R0}, Bs0, Lf, Ef, RBs) ->
- {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none),
+expr({op,_,'orelse',L0,R0}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
V = case L of
true -> true;
false ->
- {value,R,_} = expr(R0, Bs1, Lf, Ef, none),
+ {value,R,_} = expr(R0, Bs1, Lf, Ef, none, FUVs),
R;
_ -> erlang:raise(error, {badarg,L}, ?STACKTRACE)
end,
ret_expr(V, Bs1, RBs);
-expr({op,_,Op,L0,R0}, Bs0, Lf, Ef, RBs) ->
- {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none),
- {value,R,Bs2} = expr(R0, Bs0, Lf, Ef, none),
+expr({op,_,Op,L0,R0}, Bs0, Lf, Ef, RBs, FUVs) ->
+ {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
+ {value,R,Bs2} = expr(R0, Bs0, Lf, Ef, none, FUVs),
eval_op(Op, L, R, merge_bindings(Bs1, Bs2), Ef, RBs);
-expr({bin,_,Fs}, Bs0, Lf, Ef, RBs) ->
- EvalFun = fun(E, B) -> expr(E, B, Lf, Ef, none) end,
+expr({bin,_,Fs}, Bs0, Lf, Ef, RBs, FUVs) ->
+ EvalFun = fun(E, B) -> expr(E, B, Lf, Ef, none, FUVs) end,
{value,V,Bs} = eval_bits:expr_grp(Fs, Bs0, EvalFun),
ret_expr(V, Bs, RBs);
-expr({remote,_,_,_}, _Bs, _Lf, _Ef, _RBs) ->
+expr({remote,_,_,_}, _Bs, _Lf, _Ef, _RBs, _FUVs) ->
erlang:raise(error, {badexpr,':'}, ?STACKTRACE);
-expr({value,_,Val}, Bs, _Lf, _Ef, RBs) -> % Special case straight values.
+expr({value,_,Val}, Bs, _Lf, _Ef, RBs, _FUVs) -> % Special case straight values.
ret_expr(Val, Bs, RBs).
find_maxline(LC) ->
@@ -496,6 +523,26 @@ find_maxline(LC) ->
_ = erl_parse:map_anno(F, LC),
erase('$erl_eval_max_line').
+fun_used_bindings(Fun, Cs, Bs, FUVs) ->
+ {Used,InnerFUVs} =
+ case FUVs of
+ %% If this clause is in our fun used vars tree,
+ %% then we do not need to compute to traverse it again.
+ #{Cs := UsedAndFUVs} ->
+ UsedAndFUVs;
+
+ #{} ->
+ %% Save only used variables in the function environment.
+ AllUsedVars = erl_lint:used_vars([Fun], bindings(Bs)),
+
+ %% At the root we should see only a single function,
+ %% so we extract its used vars and its tree out.
+ [{_,UsedAndFUVs}] = maps:to_list(AllUsedVars),
+ UsedAndFUVs
+ end,
+
+ {filter_bindings(fun(K,_V) -> member(K,Used) end, Bs),InnerFUVs}.
+
hide_calls(LC, MaxLine) ->
LineId0 = MaxLine + 1,
{NLC, _, D} = hide(LC, LineId0, maps:new()),
@@ -507,19 +554,19 @@ hide({call,A,{atom,_,N}=Atom,Args}, Id0, D0) ->
C = case erl_internal:bif(N, length(Args)) of
true ->
{call,A,Atom,NArgs};
- false ->
+ false ->
Anno = erl_anno:new(Id),
{call,Anno,{remote,A,{atom,A,m},{atom,A,f}},NArgs}
end,
{C, Id+1, maps:put(Id, {call,Atom}, D)};
-hide(T0, Id0, D0) when is_tuple(T0) ->
+hide(T0, Id0, D0) when is_tuple(T0) ->
{L, Id, D} = hide(tuple_to_list(T0), Id0, D0),
{list_to_tuple(L), Id, D};
-hide([E0 | Es0], Id0, D0) ->
+hide([E0 | Es0], Id0, D0) ->
{E, Id1, D1} = hide(E0, Id0, D0),
{Es, Id, D} = hide(Es0, Id1, D1),
{[E | Es], Id, D};
-hide(E, Id, D) ->
+hide(E, Id, D) ->
{E, Id, D}.
unhide_calls({call,Anno,{remote,A,{atom,A,m},{atom,A,f}}=F,Args},
@@ -532,41 +579,41 @@ unhide_calls({call,Anno,{remote,A,{atom,A,m},{atom,A,f}}=F,Args},
true ->
{call,Anno,F,unhide_calls(Args, MaxLine, D)}
end;
-unhide_calls(T, MaxLine, D) when is_tuple(T) ->
+unhide_calls(T, MaxLine, D) when is_tuple(T) ->
list_to_tuple(unhide_calls(tuple_to_list(T), MaxLine, D));
-unhide_calls([E | Es], MaxLine, D) ->
+unhide_calls([E | Es], MaxLine, D) ->
[unhide_calls(E, MaxLine, D) | unhide_calls(Es, MaxLine, D)];
-unhide_calls(E, _MaxLine, _D) ->
+unhide_calls(E, _MaxLine, _D) ->
E.
%% local_func(Function, Arguments, Bindings, LocalFuncHandler,
-%% ExternalFuncHandler, RBs) ->
+%% ExternalFuncHandler, RBs, FunUsedVars) ->
%% {value,Value,Bindings} | Value when
%% LocalFuncHandler = {value,F} | {value,F,Eas} |
%% {eval,F} | {eval,F,Eas} | none.
-local_func(Func, As0, Bs0, {value,F}, Ef, value) ->
- {As1,_Bs1} = expr_list(As0, Bs0, {value,F}, Ef),
+local_func(Func, As0, Bs0, {value,F}, Ef, value, FUVs) ->
+ {As1,_Bs1} = expr_list(As0, Bs0, {value,F}, Ef, FUVs),
%% Make tail recursive calls when possible.
F(Func, As1);
-local_func(Func, As0, Bs0, {value,F}, Ef, RBs) ->
- {As1,Bs1} = expr_list(As0, Bs0, {value,F}, Ef),
+local_func(Func, As0, Bs0, {value,F}, Ef, RBs, FUVs) ->
+ {As1,Bs1} = expr_list(As0, Bs0, {value,F}, Ef, FUVs),
ret_expr(F(Func, As1), Bs1, RBs);
-local_func(Func, As0, Bs0, {value,F,Eas}, Ef, RBs) ->
+local_func(Func, As0, Bs0, {value,F,Eas}, Ef, RBs, FUVs) ->
Fun = fun(Name, Args) -> apply(F, [Name,Args|Eas]) end,
- local_func(Func, As0, Bs0, {value, Fun}, Ef, RBs);
-local_func(Func, As, Bs, {eval,F}, _Ef, RBs) ->
+ local_func(Func, As0, Bs0, {value, Fun}, Ef, RBs, FUVs);
+local_func(Func, As, Bs, {eval,F}, _Ef, RBs, _FUVs) ->
local_func2(F(Func, As, Bs), RBs);
-local_func(Func, As, Bs, {eval,F,Eas}, _Ef, RBs) ->
+local_func(Func, As, Bs, {eval,F,Eas}, _Ef, RBs, _FUVs) ->
local_func2(apply(F, [Func,As,Bs|Eas]), RBs);
%% These two clauses are for backwards compatibility.
-local_func(Func, As0, Bs0, {M,F}, Ef, RBs) ->
- {As1,Bs1} = expr_list(As0, Bs0, {M,F}, Ef),
+local_func(Func, As0, Bs0, {M,F}, Ef, RBs, FUVs) ->
+ {As1,Bs1} = expr_list(As0, Bs0, {M,F}, Ef, FUVs),
ret_expr(M:F(Func,As1), Bs1, RBs);
-local_func(Func, As, _Bs, {M,F,Eas}, _Ef, RBs) ->
+local_func(Func, As, _Bs, {M,F,Eas}, _Ef, RBs, _FUVs) ->
local_func2(apply(M, F, [Func,As|Eas]), RBs);
%% Default unknown function handler to undefined function.
-local_func(Func, As0, _Bs0, none, _Ef, _RBs) ->
+local_func(Func, As0, _Bs0, none, _Ef, _RBs, _FUVs) ->
erlang:raise(error, undef, [{?MODULE,Func,length(As0)}|?STACKTRACE]).
local_func2({value,V,Bs}, RBs) ->
@@ -609,7 +656,7 @@ do_apply({M,F}=Func, As, Bs0, Ef, RBs)
end;
do_apply(Func, As, Bs0, Ef, RBs) ->
Env = if
- is_function(Func) ->
+ is_function(Func) ->
case {erlang:fun_info(Func, module),
erlang:fun_info(Func, env)} of
{{module,?MODULE},{env,Env1}} when Env1 =/= [] ->
@@ -617,12 +664,12 @@ do_apply(Func, As, Bs0, Ef, RBs) ->
_ ->
no_env
end;
- true ->
+ true ->
no_env
end,
case {Env,Ef} of
- {{env,[{FBs,FLf,FEf,FCs}]},_} ->
- %% If we are evaluting within another function body
+ {{env,[{FBs,FLf,FEf,FFUVs,FCs}]},_} ->
+ %% If we are evaluting within another function body
%% (RBs =/= none), we return RBs when this function body
%% has been evalutated, otherwise we return Bs0, the
%% bindings when evalution of this function body started.
@@ -632,18 +679,18 @@ do_apply(Func, As, Bs0, Ef, RBs) ->
end,
case {erlang:fun_info(Func, arity), length(As)} of
{{arity, Arity}, Arity} ->
- eval_fun(FCs, As, FBs, FLf, FEf, NRBs);
+ eval_fun(FCs, As, FBs, FLf, FEf, NRBs, FFUVs);
_ ->
erlang:raise(error, {badarity,{Func,As}},?STACKTRACE)
end;
- {{env,[{FBs,FLf,FEf,FCs,FName}]},_} ->
+ {{env,[{FBs,FLf,FEf,FFUVs,FCs,FName}]},_} ->
NRBs = if
RBs =:= none -> Bs0;
true -> RBs
end,
case {erlang:fun_info(Func, arity), length(As)} of
{{arity, Arity}, Arity} ->
- eval_named_fun(FCs, As, FBs, FLf, FEf, FName, Func, NRBs);
+ eval_named_fun(FCs, As, FBs, FLf, FEf, FName, Func, NRBs, FFUVs);
_ ->
erlang:raise(error, {badarity,{Func,As}},?STACKTRACE)
end;
@@ -671,48 +718,48 @@ do_apply(Mod, Func, As, Bs0, Ef, RBs) ->
ret_expr(F({Mod,Func}, As), Bs0, RBs)
end.
-%% eval_lc(Expr, [Qualifier], Bindings, LocalFunctionHandler,
+%% eval_lc(Expr, [Qualifier], Bindings, LocalFunctionHandler,
%% ExternalFuncHandler, RetBindings) ->
%% {value,Value,Bindings} | Value
-eval_lc(E, Qs, Bs, Lf, Ef, RBs) ->
- ret_expr(lists:reverse(eval_lc1(E, Qs, Bs, Lf, Ef, [])), Bs, RBs).
+eval_lc(E, Qs, Bs, Lf, Ef, RBs, FUVs) ->
+ ret_expr(lists:reverse(eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, [])), Bs, RBs).
-eval_lc1(E, [{generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) ->
- {value,L1,_Bs1} = expr(L0, Bs0, Lf, Ef, none),
- CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, Acc) end,
+eval_lc1(E, [{generate,_,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
+ {value,L1,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
+ CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
eval_generate(L1, P, Bs0, Lf, Ef, CompFun, Acc0);
-eval_lc1(E, [{b_generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) ->
- {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none),
- CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, Acc) end,
+eval_lc1(E, [{b_generate,_,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
+ {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
+ CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
eval_b_generate(Bin, P, Bs0, Lf, Ef, CompFun, Acc0);
-eval_lc1(E, [F|Qs], Bs0, Lf, Ef, Acc) ->
- CompFun = fun(Bs) -> eval_lc1(E, Qs, Bs, Lf, Ef, Acc) end,
- eval_filter(F, Bs0, Lf, Ef, CompFun, Acc);
-eval_lc1(E, [], Bs, Lf, Ef, Acc) ->
- {value,V,_} = expr(E, Bs, Lf, Ef, none),
+eval_lc1(E, [F|Qs], Bs0, Lf, Ef, FUVs, Acc) ->
+ CompFun = fun(Bs) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
+ eval_filter(F, Bs0, Lf, Ef, CompFun, FUVs, Acc);
+eval_lc1(E, [], Bs, Lf, Ef, FUVs, Acc) ->
+ {value,V,_} = expr(E, Bs, Lf, Ef, none, FUVs),
[V|Acc].
-%% eval_bc(Expr, [Qualifier], Bindings, LocalFunctionHandler,
+%% eval_bc(Expr, [Qualifier], Bindings, LocalFunctionHandler,
%% ExternalFuncHandler, RetBindings) ->
%% {value,Value,Bindings} | Value
-eval_bc(E, Qs, Bs, Lf, Ef, RBs) ->
- ret_expr(eval_bc1(E, Qs, Bs, Lf, Ef, <<>>), Bs, RBs).
+eval_bc(E, Qs, Bs, Lf, Ef, RBs, FUVs) ->
+ ret_expr(eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, <<>>), Bs, RBs).
-eval_bc1(E, [{b_generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) ->
- {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none),
- CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, Acc) end,
+eval_bc1(E, [{b_generate,_,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
+ {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
+ CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
eval_b_generate(Bin, P, Bs0, Lf, Ef, CompFun, Acc0);
-eval_bc1(E, [{generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) ->
- {value,List,_Bs1} = expr(L0, Bs0, Lf, Ef, none),
- CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, Acc) end,
+eval_bc1(E, [{generate,_,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
+ {value,List,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
+ CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
eval_generate(List, P, Bs0, Lf, Ef, CompFun, Acc0);
-eval_bc1(E, [F|Qs], Bs0, Lf, Ef, Acc) ->
- CompFun = fun(Bs) -> eval_bc1(E, Qs, Bs, Lf, Ef, Acc) end,
- eval_filter(F, Bs0, Lf, Ef, CompFun, Acc);
-eval_bc1(E, [], Bs, Lf, Ef, Acc) ->
- {value,V,_} = expr(E, Bs, Lf, Ef, none),
+eval_bc1(E, [F|Qs], Bs0, Lf, Ef, FUVs, Acc) ->
+ CompFun = fun(Bs) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end,
+ eval_filter(F, Bs0, Lf, Ef, CompFun, FUVs, Acc);
+eval_bc1(E, [], Bs, Lf, Ef, FUVs, Acc) ->
+ {value,V,_} = expr(E, Bs, Lf, Ef, none, FUVs),
<<Acc/bitstring,V/bitstring>>.
eval_generate([V|Rest], P, Bs0, Lf, Ef, CompFun, Acc) ->
@@ -721,7 +768,7 @@ eval_generate([V|Rest], P, Bs0, Lf, Ef, CompFun, Acc) ->
Bs2 = add_bindings(Bsn, Bs0),
NewAcc = CompFun(Bs2, Acc),
eval_generate(Rest, P, Bs0, Lf, Ef, CompFun, NewAcc);
- nomatch ->
+ nomatch ->
eval_generate(Rest, P, Bs0, Lf, Ef, CompFun, Acc)
end;
eval_generate([], _P, _Bs0, _Lf, _Ef, _CompFun, Acc) ->
@@ -745,7 +792,7 @@ eval_b_generate(<<_/bitstring>>=Bin, P, Bs0, Lf, Ef, CompFun, Acc) ->
eval_b_generate(Term, _P, _Bs0, _Lf, _Ef, _CompFun, _Acc) ->
erlang:raise(error, {bad_generator,Term}, ?STACKTRACE).
-eval_filter(F, Bs0, Lf, Ef, CompFun, Acc) ->
+eval_filter(F, Bs0, Lf, Ef, CompFun, FUVs, Acc) ->
case erl_lint:is_guard_test(F) of
true ->
case guard_test(F, Bs0, Lf, Ef) of
@@ -753,10 +800,10 @@ eval_filter(F, Bs0, Lf, Ef, CompFun, Acc) ->
{value,false,_} -> Acc
end;
false ->
- case expr(F, Bs0, Lf, Ef, none) of
+ case expr(F, Bs0, Lf, Ef, none, FUVs) of
{value,true,Bs1} -> CompFun(Bs1);
{value,false,_} -> Acc;
- {value,V,_} ->
+ {value,V,_} ->
erlang:raise(error, {bad_filter,V}, ?STACKTRACE)
end
end.
@@ -765,18 +812,18 @@ eval_filter(F, Bs0, Lf, Ef, CompFun, Acc) ->
%% ExternalFuncHandler) ->
%% {[{map_assoc | map_exact,Key,Value}],Bindings}
-eval_map_fields(Fs, Bs, Lf, Ef) ->
- eval_map_fields(Fs, Bs, Lf, Ef, []).
-
-eval_map_fields([{map_field_assoc,_,K0,V0}|Fs], Bs0, Lf, Ef, Acc) ->
- {value,K1,Bs1} = expr(K0, Bs0, Lf, Ef, none),
- {value,V1,Bs2} = expr(V0, Bs1, Lf, Ef, none),
- eval_map_fields(Fs, Bs2, Lf, Ef, [{map_assoc,K1,V1}|Acc]);
-eval_map_fields([{map_field_exact,_,K0,V0}|Fs], Bs0, Lf, Ef, Acc) ->
- {value,K1,Bs1} = expr(K0, Bs0, Lf, Ef, none),
- {value,V1,Bs2} = expr(V0, Bs1, Lf, Ef, none),
- eval_map_fields(Fs, Bs2, Lf, Ef, [{map_exact,K1,V1}|Acc]);
-eval_map_fields([], Bs, _Lf, _Ef, Acc) ->
+eval_map_fields(Fs, Bs, Lf, Ef, FUVs) ->
+ eval_map_fields(Fs, Bs, Lf, Ef, FUVs, []).
+
+eval_map_fields([{map_field_assoc,_,K0,V0}|Fs], Bs0, Lf, Ef, FUVs, Acc) ->
+ {value,K1,Bs1} = expr(K0, Bs0, Lf, Ef, none, FUVs),
+ {value,V1,Bs2} = expr(V0, Bs1, Lf, Ef, none, FUVs),
+ eval_map_fields(Fs, Bs2, Lf, Ef, FUVs, [{map_assoc,K1,V1}|Acc]);
+eval_map_fields([{map_field_exact,_,K0,V0}|Fs], Bs0, Lf, Ef, FUVs, Acc) ->
+ {value,K1,Bs1} = expr(K0, Bs0, Lf, Ef, none, FUVs),
+ {value,V1,Bs2} = expr(V0, Bs1, Lf, Ef, none, FUVs),
+ eval_map_fields(Fs, Bs2, Lf, Ef, FUVs, [{map_exact,K1,V1}|Acc]);
+eval_map_fields([], Bs, _Lf, _Ef, _FUVs, Acc) ->
{lists:reverse(Acc),Bs}.
@@ -793,45 +840,45 @@ ret_expr(V, _Bs, RBs) when is_list(RBs); is_map(RBs) ->
{value,V,RBs}.
%% eval_fun(Arguments, {Bindings,LocalFunctionHandler,
-%% ExternalFunctionHandler,Clauses}) -> Value
+%% ExternalFunctionHandler,FunUsedVars,Clauses}) -> Value
%% This function is called when the fun is called from compiled code
%% or from apply.
-eval_fun(As, {Bs0,Lf,Ef,Cs}) ->
- eval_fun(Cs, As, Bs0, Lf, Ef, value).
+eval_fun(As, {Bs0,Lf,Ef,FUVs,Cs}) ->
+ eval_fun(Cs, As, Bs0, Lf, Ef, value, FUVs).
-eval_fun([{clause,_,H,G,B}|Cs], As, Bs0, Lf, Ef, RBs) ->
+eval_fun([{clause,_,H,G,B}|Cs], As, Bs0, Lf, Ef, RBs, FUVs) ->
case match_list(H, As, new_bindings(Bs0), Bs0) of
{match,Bsn} -> % The new bindings for the head
Bs1 = add_bindings(Bsn, Bs0), % which then shadow!
case guard(G, Bs1, Lf, Ef) of
- true -> exprs(B, Bs1, Lf, Ef, RBs);
- false -> eval_fun(Cs, As, Bs0, Lf, Ef, RBs)
+ true -> exprs(B, Bs1, Lf, Ef, RBs, FUVs);
+ false -> eval_fun(Cs, As, Bs0, Lf, Ef, RBs, FUVs)
end;
nomatch ->
- eval_fun(Cs, As, Bs0, Lf, Ef, RBs)
+ eval_fun(Cs, As, Bs0, Lf, Ef, RBs, FUVs)
end;
-eval_fun([], As, _Bs, _Lf, _Ef, _RBs) ->
- erlang:raise(error, function_clause,
+eval_fun([], As, _Bs, _Lf, _Ef, _RBs, _FUVs) ->
+ erlang:raise(error, function_clause,
[{?MODULE,'-inside-an-interpreted-fun-',As}|?STACKTRACE]).
-eval_named_fun(As, Fun, {Bs0,Lf,Ef,Cs,Name}) ->
- eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, value).
+eval_named_fun(As, Fun, {Bs0,Lf,Ef,FUVs,Cs,Name}) ->
+ eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, value, FUVs).
-eval_named_fun([{clause,_,H,G,B}|Cs], As, Bs0, Lf, Ef, Name, Fun, RBs) ->
+eval_named_fun([{clause,_,H,G,B}|Cs], As, Bs0, Lf, Ef, Name, Fun, RBs, FUVs) ->
Bs1 = add_binding(Name, Fun, Bs0),
case match_list(H, As, new_bindings(Bs0), Bs1) of
{match,Bsn} -> % The new bindings for the head
Bs2 = add_bindings(Bsn, Bs1), % which then shadow!
case guard(G, Bs2, Lf, Ef) of
- true -> exprs(B, Bs2, Lf, Ef, RBs);
- false -> eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, RBs)
+ true -> exprs(B, Bs2, Lf, Ef, RBs, FUVs);
+ false -> eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, RBs, FUVs)
end;
nomatch ->
- eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, RBs)
+ eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, RBs, FUVs)
end;
-eval_named_fun([], As, _Bs, _Lf, _Ef, _Name, _Fun, _RBs) ->
+eval_named_fun([], As, _Bs, _Lf, _Ef, _Name, _Fun, _RBs, _FUVs) ->
erlang:raise(error, function_clause,
[{?MODULE,'-inside-an-interpreted-fun-',As}|?STACKTRACE]).
@@ -847,7 +894,7 @@ eval_named_fun([], As, _Bs, _Lf, _Ef, _Name, _Fun, _RBs) ->
ValueList :: [value()],
NewBindings :: binding_struct()).
expr_list(Es, Bs) ->
- expr_list(Es, Bs, none, none).
+ expr_list(Es, Bs, none, none, empty_fun_used_vars()).
-spec(expr_list(ExpressionList, Bindings, LocalFunctionHandler) ->
{ValueList, NewBindings} when
@@ -857,7 +904,7 @@ expr_list(Es, Bs) ->
ValueList :: [value()],
NewBindings :: binding_struct()).
expr_list(Es, Bs, Lf) ->
- expr_list(Es, Bs, Lf, none).
+ expr_list(Es, Bs, Lf, none, empty_fun_used_vars()).
-spec(expr_list(ExpressionList, Bindings, LocalFunctionHandler,
NonLocalFunctionHandler) ->
@@ -869,12 +916,15 @@ expr_list(Es, Bs, Lf) ->
ValueList :: [value()],
NewBindings :: binding_struct()).
expr_list(Es, Bs, Lf, Ef) ->
- expr_list(Es, [], Bs, Bs, Lf, Ef).
+ expr_list(Es, Bs, Lf, Ef, empty_fun_used_vars()).
+
+expr_list(Es, Bs, Lf, Ef, FUVs) ->
+ expr_list(Es, [], Bs, Bs, Lf, Ef, FUVs).
-expr_list([E|Es], Vs, BsOrig, Bs0, Lf, Ef) ->
- {value,V,Bs1} = expr(E, BsOrig, Lf, Ef, none),
- expr_list(Es, [V|Vs], BsOrig, merge_bindings(Bs1, Bs0), Lf, Ef);
-expr_list([], Vs, _, Bs, _Lf, _Ef) ->
+expr_list([E|Es], Vs, BsOrig, Bs0, Lf, Ef, FUVs) ->
+ {value,V,Bs1} = expr(E, BsOrig, Lf, Ef, none, FUVs),
+ expr_list(Es, [V|Vs], BsOrig, merge_bindings(Bs1, Bs0), Lf, Ef, FUVs);
+expr_list([], Vs, _, Bs, _Lf, _Ef, _FUVs) ->
{reverse(Vs),Bs}.
eval_op(Op, Arg1, Arg2, Bs, Ef, RBs) ->
@@ -885,26 +935,26 @@ eval_op(Op, Arg, Bs, Ef, RBs) ->
%% if_clauses(Clauses, Bindings, LocalFuncHandler, ExtFuncHandler, RBs)
-if_clauses([{clause,_,[],G,B}|Cs], Bs, Lf, Ef, RBs) ->
+if_clauses([{clause,_,[],G,B}|Cs], Bs, Lf, Ef, RBs, FUVs) ->
case guard(G, Bs, Lf, Ef) of
- true -> exprs(B, Bs, Lf, Ef, RBs);
- false -> if_clauses(Cs, Bs, Lf, Ef, RBs)
+ true -> exprs(B, Bs, Lf, Ef, RBs, FUVs);
+ false -> if_clauses(Cs, Bs, Lf, Ef, RBs, FUVs)
end;
-if_clauses([], _Bs, _Lf, _Ef, _RBs) ->
+if_clauses([], _Bs, _Lf, _Ef, _RBs, _FUVs) ->
erlang:raise(error, if_clause, ?STACKTRACE).
-%% try_clauses(Body, CaseClauses, CatchClauses, AfterBody, Bindings,
+%% try_clauses(Body, CaseClauses, CatchClauses, AfterBody, Bindings,
%% LocalFuncHandler, ExtFuncHandler, RBs)
-try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs) ->
+try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs, FUVs) ->
check_stacktrace_vars(Catches, Bs),
- try exprs(B, Bs, Lf, Ef, none) of
+ try exprs(B, Bs, Lf, Ef, none, FUVs) of
{value,V,Bs1} when Cases =:= [] ->
ret_expr(V, Bs1, RBs);
{value,V,Bs1} ->
case match_clause(Cases, [V], Bs1, Lf, Ef) of
{B2,Bs2} ->
- exprs(B2, Bs2, Lf, Ef, RBs);
+ exprs(B2, Bs2, Lf, Ef, RBs, FUVs);
nomatch ->
erlang:raise(error, {try_clause,V}, ?STACKTRACE)
end
@@ -915,15 +965,15 @@ try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs) ->
V = {Class,Reason,Stacktrace},
case match_clause(Catches, [V], Bs, Lf, Ef) of
{B2,Bs2} ->
- exprs(B2, Bs2, Lf, Ef, RBs);
+ exprs(B2, Bs2, Lf, Ef, RBs, FUVs);
nomatch ->
erlang:raise(Class, Reason, Stacktrace)
end
after
- if AB =:= [] ->
+ if AB =:= [] ->
Bs; % any
true ->
- exprs(AB, Bs, Lf, Ef, none)
+ exprs(AB, Bs, Lf, Ef, none, FUVs)
end
end.
@@ -944,13 +994,13 @@ check_stacktrace_vars([{clause,_,[{tuple,_,[_,_,STV]}],_,_}|Cs], Bs) ->
check_stacktrace_vars([], _Bs) ->
ok.
-%% case_clauses(Value, Clauses, Bindings, LocalFuncHandler, ExtFuncHandler,
+%% case_clauses(Value, Clauses, Bindings, LocalFuncHandler, ExtFuncHandler,
%% RBs)
-case_clauses(Val, Cs, Bs, Lf, Ef, RBs) ->
+case_clauses(Val, Cs, Bs, Lf, Ef, RBs, FUVs) ->
case match_clause(Cs, [Val], Bs, Lf, Ef) of
{B, Bs1} ->
- exprs(B, Bs1, Lf, Ef, RBs);
+ exprs(B, Bs1, Lf, Ef, RBs, FUVs);
nomatch ->
erlang:raise(error, {case_clause,Val}, ?STACKTRACE)
end.
@@ -958,20 +1008,20 @@ case_clauses(Val, Cs, Bs, Lf, Ef, RBs) ->
%%
%% receive_clauses(Clauses, Bindings, LocalFuncHnd,ExtFuncHnd, RBs)
%%
-receive_clauses(Cs, Bs, Lf, Ef, RBs) ->
- receive_clauses(infinity, Cs, unused, Bs, Lf, Ef, RBs).
+receive_clauses(Cs, Bs, Lf, Ef, RBs, FUVs) ->
+ receive_clauses(infinity, Cs, unused, Bs, Lf, Ef, RBs, FUVs).
%%
-%% receive_clauses(TimeOut, Clauses, TimeoutBody, Bindings,
+%% receive_clauses(TimeOut, Clauses, TimeoutBody, Bindings,
%% ExternalFuncHandler, LocalFuncHandler, RBs)
%%
-receive_clauses(T, Cs, TB, Bs, Lf, Ef, RBs) ->
+receive_clauses(T, Cs, TB, Bs, Lf, Ef, RBs, FUVs) ->
F = fun (M) -> match_clause(Cs, [M], Bs, Lf, Ef) end,
case prim_eval:'receive'(F, T) of
{B, Bs1} ->
- exprs(B, Bs1, Lf, Ef, RBs);
+ exprs(B, Bs1, Lf, Ef, RBs, FUVs);
timeout ->
{B, Bs1} = TB,
- exprs(B, Bs1, Lf, Ef, RBs)
+ exprs(B, Bs1, Lf, Ef, RBs, FUVs)
end.
%% match_clause -> {Body, Bindings} or nomatch
@@ -1050,7 +1100,7 @@ guard_test(G, Bs0, Lf, Ef) ->
expr_guard_test(G, Bs0, Lf, Ef) ->
try {value,true,_} = expr(G, Bs0, Lf, Ef, none)
catch error:_ -> {value,false,Bs0} end.
-
+
type_test(integer) -> is_integer;
type_test(float) -> is_float;
type_test(number) -> is_number;
@@ -1140,7 +1190,7 @@ match1({cons,_,H,T}, [H1|T1], Bs0, BBs) ->
match1(T, T1, Bs, BBs);
match1({cons,_,_,_}, _, _Bs, _BBs) ->
throw(nomatch);
-match1({tuple,_,Elts}, Tuple, Bs, BBs)
+match1({tuple,_,Elts}, Tuple, Bs, BBs)
when length(Elts) =:= tuple_size(Tuple) ->
match_tuple(Elts, Tuple, 1, Bs, BBs);
match1({tuple,_,_}, _, _Bs, _BBs) ->
@@ -1287,7 +1337,7 @@ merge_bindings(Bs1, Bs2) ->
foldl(fun ({Name,Val}, Bs) ->
case orddict:find(Name, Bs) of
{ok,Val} -> Bs; %Already with SAME value
- {ok,V1} ->
+ {ok,V1} ->
erlang:raise(error, {badmatch,V1}, ?STACKTRACE);
error -> orddict:store(Name, Val, Bs)
end end,
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 3693a3d439..b9f737297b 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -31,6 +31,7 @@
-export([check_format_string/1]).
-export_type([error_info/0, error_description/0]).
+-export_type([fun_used_vars/0]). % Used from erl_eval.erl.
-import(lists, [all/2,any/2,
foldl/3,foldr/3,
@@ -103,6 +104,8 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
-type used_type() :: #used_type{}.
+-type fun_used_vars() :: #{erl_parse:abstract_expr() => {[atom()], fun_used_vars()}}.
+
%% Usage of records, functions, and imports. The variable table, which
%% is passed on as an argument, holds the usage of variables.
-record(usage, {
@@ -151,6 +154,8 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
%outside any fun or lc
xqlc= false :: boolean(), %true if qlc.hrl included
called= [] :: [{fa(),anno()}], %Called functions
+ fun_used_vars = undefined %Funs used vars
+ :: fun_used_vars() | undefined,
usage = #usage{} :: #usage{},
specs = maps:new() %Type specifications
:: #{mfa() => anno()},
@@ -529,10 +534,9 @@ used_vars(Exprs, BindingsList) ->
({V,_Val}, Vs0) -> [{V,{bound,unused,[]}} | Vs0]
end, [], BindingsList),
Vt = orddict:from_list(Vs),
- {Evt,_St} = exprs(set_file(Exprs, "nofile"), Vt, start()),
- {ok, foldl(fun({V,{_,used,_}}, L) -> [V | L];
- (_, L) -> L
- end, [], Evt)}.
+ St0 = (start())#lint{fun_used_vars=maps:new()},
+ {_Evt,St1} = exprs(Exprs, Vt, St0),
+ St1#lint.fun_used_vars.
%% module([Form]) ->
%% module([Form], FileName) ->
@@ -3613,7 +3617,20 @@ handle_bitstring_gen_pat(_,St) ->
%% unless it was introduced in a fun or an lc. Only if pat_var finds
%% such variables can the correct line number be given.
+%% If fun_used_vars is set, we want to compute the tree of used
+%% vars across functions. This is by erl_eval to compute used vars
+%% without having to traverse the tree multiple times.
+
+fun_clauses(Cs, Vt, #lint{fun_used_vars=(#{}=FUV)} = St) ->
+ {Uvt, St0} = fun_clauses1(Cs, Vt, St#lint{fun_used_vars=maps:new()}),
+ #lint{fun_used_vars=InnerFUV} = St0,
+ UsedVars = [V || {V, {_, used, _}} <- Uvt],
+ OuterFUV = maps:put(Cs, {UsedVars, InnerFUV}, FUV),
+ {Uvt, St0#lint{fun_used_vars=OuterFUV}};
fun_clauses(Cs, Vt, St) ->
+ fun_clauses1(Cs, Vt, St).
+
+fun_clauses1(Cs, Vt, St) ->
OldRecDef = St#lint.recdef_top,
{Bvt,St2} = foldl(fun (C, {Bvt0, St0}) ->
{Cvt,St1} = fun_clause(C, Vt, St0),
diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl
index 0bf59cf60e..f4d58e61c5 100644
--- a/lib/stdlib/test/erl_eval_SUITE.erl
+++ b/lib/stdlib/test/erl_eval_SUITE.erl
@@ -1000,14 +1000,14 @@ otp_14826(_Config) ->
backtrace_check("fun(P) when is_pid(P) -> true end(a).",
function_clause,
[{erl_eval,'-inside-an-interpreted-fun-',[a],[]},
- {erl_eval,eval_fun,6},
+ {erl_eval,eval_fun,7},
?MODULE]),
backtrace_check("B.",
{unbound_var, 'B'},
[{erl_eval,expr,2}, ?MODULE]),
backtrace_check("B.",
{unbound, 'B'},
- [{erl_eval,expr,5}, ?MODULE],
+ [{erl_eval,expr,6}, ?MODULE],
none, none),
backtrace_check("1/0.",
badarith,
@@ -1037,18 +1037,18 @@ otp_14826(_Config) ->
{erl_eval,do_apply,6}]),
backtrace_check("try a of b -> bar after foo end.",
{try_clause,a},
- [{erl_eval,try_clauses,8}]),
+ [{erl_eval,try_clauses,9}]),
check(fun() -> X = try foo:bar() catch A:B:C -> {A,B} end, X end,
"try foo:bar() catch A:B:C -> {A,B} end.",
{error, undef}),
backtrace_check("C = 4, try foo:bar() catch A:B:C -> {A,B,C} end.",
stacktrace_bound,
[{erl_eval,check_stacktrace_vars,2},
- {erl_eval,try_clauses,8}],
+ {erl_eval,try_clauses,9}],
none, none),
backtrace_catch("catch (try a of b -> bar after foo end).",
{try_clause,a},
- [{erl_eval,try_clauses,8}]),
+ [{erl_eval,try_clauses,9}]),
backtrace_check("try 1/0 catch exit:a -> foo end.",
badarith,
[{erlang,'/',[1,0],[]},
@@ -1063,7 +1063,7 @@ otp_14826(_Config) ->
catch
error:{illegal_stacktrace_variable,{atom,1,'C'}}:S ->
[{erl_eval,check_stacktrace_vars,2,_},
- {erl_eval,try_clauses,8,_}|_] = S
+ {erl_eval,try_clauses,9,_}|_] = S
end,
backtrace_check("{1,1} = {A = 1, A = 2}.",
{badmatch, 1},
@@ -1076,24 +1076,24 @@ otp_14826(_Config) ->
[{erl_eval,match,4}], none, none),
backtrace_check("case a of b -> ok end.",
{case_clause,a},
- [{erl_eval,case_clauses,6}, ?MODULE]),
+ [{erl_eval,case_clauses,7}, ?MODULE]),
backtrace_check("if a =:= b -> ok end.",
if_clause,
- [{erl_eval,if_clauses,5}, ?MODULE]),
+ [{erl_eval,if_clauses,6}, ?MODULE]),
backtrace_check("fun A(b) -> ok end(a).",
function_clause,
[{erl_eval,'-inside-an-interpreted-fun-',[a],[]},
- {erl_eval,eval_named_fun,8},
+ {erl_eval,eval_named_fun,9},
?MODULE]),
backtrace_check("[A || A <- a].",
{bad_generator, a},
- [{erl_eval,eval_generate,7}, {erl_eval, eval_lc, 6}]),
+ [{erl_eval,eval_generate,7}, {erl_eval, eval_lc, 7}]),
backtrace_check("<< <<A>> || <<A>> <= a>>.",
{bad_generator, a},
- [{erl_eval,eval_b_generate,7}, {erl_eval, eval_bc, 6}]),
+ [{erl_eval,eval_b_generate,7}, {erl_eval, eval_bc, 7}]),
backtrace_check("[A || A <- [1], begin a end].",
{bad_filter, a},
- [{erl_eval,eval_filter,6}, {erl_eval, eval_generate, 7}]),
+ [{erl_eval,eval_filter,7}, {erl_eval, eval_generate, 7}]),
fun() ->
{'EXIT', {{badarity, {_Fun, []}}, BT}} =
(catch parse_and_run("fun(A) -> A end().")),
@@ -1106,20 +1106,20 @@ otp_14826(_Config) ->
end(),
backtrace_check("foo().",
undef,
- [{erl_eval,foo,0},{erl_eval,local_func,6}],
+ [{erl_eval,foo,0},{erl_eval,local_func,7}],
none, none),
backtrace_check("a orelse false.",
{badarg, a},
- [{erl_eval,expr,5}, ?MODULE]),
+ [{erl_eval,expr,6}, ?MODULE]),
backtrace_check("a andalso false.",
{badarg, a},
- [{erl_eval,expr,5}, ?MODULE]),
+ [{erl_eval,expr,6}, ?MODULE]),
backtrace_check("t = u.",
{badmatch, u},
- [{erl_eval,expr,5}, ?MODULE]),
+ [{erl_eval,expr,6}, ?MODULE]),
backtrace_check("{math,sqrt}(2).",
{badfun, {math,sqrt}},
- [{erl_eval,expr,5}, ?MODULE]),
+ [{erl_eval,expr,6}, ?MODULE]),
backtrace_check("erl_eval_SUITE:simple().",
simple,
[{?MODULE,simple1,0},{?MODULE,simple,0},erl_eval]),
@@ -1128,15 +1128,15 @@ otp_14826(_Config) ->
"19,20,21,22,23,24,25,26,27,28,29,30) -> a end.",
{argument_limit,
{'fun',1,[{clause,1,Args,[],[{atom,1,a}]}]}},
- [{erl_eval,expr,5}, ?MODULE]),
+ [{erl_eval,expr,6}, ?MODULE]),
backtrace_check("fun F(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,"
"19,20,21,22,23,24,25,26,27,28,29,30) -> a end.",
{argument_limit,
{named_fun,1,'F',[{clause,1,Args,[],[{atom,1,a}]}]}},
- [{erl_eval,expr,5}, ?MODULE]),
+ [{erl_eval,expr,6}, ?MODULE]),
backtrace_check("#r{}.",
{undef_record,r},
- [{erl_eval,expr,5}, ?MODULE],
+ [{erl_eval,expr,6}, ?MODULE],
none, none),
%% eval_bits
backtrace_check("<<100:8/bitstring>>.",
--
2.31.1