Skip to content

Commit 7f70f64

Browse files
committed
Do not include module variables in prune_binding
1 parent 2acd094 commit 7f70f64

File tree

10 files changed

+102
-35
lines changed

10 files changed

+102
-35
lines changed

lib/elixir/lib/code.ex

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,10 @@ defmodule Code do
863863
## Options
864864
865865
* `:prune_binding` - (since v1.14.2) prune binding to keep only
866-
variables read or written by the evaluated code
866+
variables read or written by the evaluated code. Note that
867+
variables used by modules are always pruned, even if later used
868+
by the modules. You can submit to the `:on_module` tracer event
869+
and access the variables used by the module from its environment.
867870
868871
"""
869872
@doc since: "1.14.0"

lib/elixir/lib/kernel.ex

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4800,11 +4800,27 @@ defmodule Kernel do
48004800
:elixir_quote.escape(block, :none, false)
48014801
end
48024802

4803-
module_vars = :lists.map(&module_var/1, :maps.keys(env.versioned_vars))
4803+
versioned_vars = env.versioned_vars
4804+
prune = :erlang.is_map_key({:elixir, :prune_binding}, versioned_vars)
4805+
4806+
var_meta =
4807+
case prune do
4808+
true -> [generated: true, keep_unused: true]
4809+
false -> [generated: false]
4810+
end
4811+
4812+
module_vars = :lists.map(&module_var(&1, var_meta), :maps.keys(versioned_vars))
48044813

48054814
quote do
48064815
unquote(with_alias)
4807-
:elixir_module.compile(unquote(expanded), unquote(escaped), unquote(module_vars), __ENV__)
4816+
4817+
:elixir_module.compile(
4818+
unquote(expanded),
4819+
unquote(escaped),
4820+
unquote(module_vars),
4821+
unquote(prune),
4822+
__ENV__
4823+
)
48084824
end
48094825
end
48104826

@@ -4853,8 +4869,8 @@ defmodule Kernel do
48534869
{module, module, nil}
48544870
end
48554871

4856-
defp module_var({name, kind}) when is_atom(kind), do: {name, [generated: true], kind}
4857-
defp module_var({name, kind}), do: {name, [counter: kind, generated: true], nil}
4872+
defp module_var({name, kind}, meta) when is_atom(kind), do: {name, meta, kind}
4873+
defp module_var({name, kind}, meta), do: {name, [counter: kind] ++ meta, nil}
48584874

48594875
@doc ~S"""
48604876
Defines a public function with the given name and body.

lib/elixir/lib/module.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@ defmodule Module do
822822
next = :elixir_module.next_counter(nil)
823823
meta = Keyword.take(opts, [:line, :generated])
824824
quoted = :elixir_quote.linify_with_context_counter(meta, {module, next}, quoted)
825-
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
825+
:elixir_module.compile(module, quoted, [], false, :elixir.env_for_eval(opts))
826826
end
827827

828828
@doc """

lib/elixir/src/elixir.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ eval_forms(Tree, Binding, OrigE) ->
270270
eval_forms(Tree, Binding, OrigE, []).
271271
eval_forms(Tree, Binding, OrigE, Opts) ->
272272
Prune = proplists:get_value(prune_binding, Opts, false),
273-
{ExVars, ErlVars, ErlBinding} = elixir_erl_var:load_binding(Binding),
273+
{ExVars, ErlVars, ErlBinding} = elixir_erl_var:load_binding(Binding, Prune),
274274
E = elixir_env:with_vars(OrigE, ExVars),
275275
ExS = elixir_env:env_to_ex(E),
276276
ErlS = elixir_erl_var:from_env(E, ErlVars),
@@ -292,8 +292,8 @@ eval_forms(Tree, Binding, OrigE, Opts) ->
292292

293293
ExternalHandler = eval_external_handler(NewE),
294294
{value, Value, NewBinding} = erl_eval:exprs(Exprs, ErlBinding, none, ExternalHandler),
295+
PruneBefore = if Prune -> length(Binding); true -> -1 end,
295296

296-
PruneBefore = if Prune -> length(Binding); true -> 0 end,
297297
{DumpedBinding, DumpedVars} =
298298
elixir_erl_var:dump_binding(NewBinding, NewErlS, NewExS, PruneBefore),
299299

lib/elixir/src/elixir_bootstrap.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
'MACRO-defmodule'(_Caller, Alias, [{do, Block}]) ->
2222
Escaped = elixir_quote:escape(Block, none, false),
23-
Args = [Alias, Escaped, [], env()],
23+
Args = [Alias, Escaped, [], false, env()],
2424
{{'.', [], [elixir_module, compile]}, [], Args}.
2525

2626
'__info__'(functions) ->

lib/elixir/src/elixir_compiler.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ compile(Quoted, ArgsList, E) ->
4848
elixir_erl_compiler:spawn(fun() -> spawned_compile(Expanded, E) end),
4949

5050
Args = list_to_tuple(ArgsList),
51-
{dispatch(Module, Fun, Args, Purgeable), EE}.
51+
{dispatch(Module, Fun, Args, Purgeable), SE, EE}.
5252

5353
spawned_compile(ExExprs, #{line := Line, file := File} = E) ->
5454
{Vars, S} = elixir_erl_var:from_env(E),
@@ -127,7 +127,7 @@ fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
127127
end,
128128

129129
ContextModules = [Expanded | ?key(E, context_modules)],
130-
elixir_module:compile(Expanded, Block, [], E#{context_modules := ContextModules}).
130+
elixir_module:compile(Expanded, Block, [], false, E#{context_modules := ContextModules}).
131131

132132
%% Bootstrapper
133133

lib/elixir/src/elixir_erl_var.erl

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
-module(elixir_erl_var).
33
-export([
44
translate/4, assign/2, build/2,
5-
load_binding/1, dump_binding/4,
5+
load_binding/2, dump_binding/4,
66
from_env/1, from_env/2
77
]).
88
-include("elixir.hrl").
@@ -65,16 +65,16 @@ to_erl_vars([], _Counter) ->
6565
to_erl_var(Counter) ->
6666
list_to_atom("_@" ++ integer_to_list(Counter)).
6767

68-
load_binding(Binding) ->
69-
load_binding(Binding, #{}, [], [], 0).
68+
load_binding(Binding, Prune) ->
69+
load_binding(Binding, #{}, [], [], 0, Prune).
7070

71-
load_binding([Binding | NextBindings], ExVars, ErlVars, Normalized, Counter) ->
71+
load_binding([Binding | NextBindings], ExVars, ErlVars, Normalized, Counter, Prune) ->
7272
{Pair, Value} = load_pair(Binding),
7373

7474
case ExVars of
7575
#{Pair := VarCounter} ->
7676
ErlVar = to_erl_var(VarCounter),
77-
load_binding(NextBindings, ExVars, ErlVars, [{ErlVar, Value} | Normalized], Counter);
77+
load_binding(NextBindings, ExVars, ErlVars, [{ErlVar, Value} | Normalized], Counter, Prune);
7878

7979
#{} ->
8080
ErlVar = to_erl_var(Counter),
@@ -84,10 +84,13 @@ load_binding([Binding | NextBindings], ExVars, ErlVars, Normalized, Counter) ->
8484
ExVars#{Pair => Counter},
8585
[{Counter, ErlVar} | ErlVars],
8686
[{ErlVar, Value} | Normalized],
87-
Counter + 1
87+
Counter + 1,
88+
Prune
8889
)
8990
end;
90-
load_binding([], ExVars, ErlVars, Normalized, _Counter) ->
91+
load_binding([], ExVars, ErlVars, Normalized, Counter, true) ->
92+
load_binding([{{elixir, prune_binding}, true}], ExVars, ErlVars, Normalized, Counter, false);
93+
load_binding([], ExVars, ErlVars, Normalized, _Counter, false) ->
9194
%% TODO: Remove me once we require Erlang/OTP 24+
9295
%% Also revisit dump_binding below and remove the vars field for simplicity.
9396
Mod =
@@ -109,7 +112,7 @@ dump_binding(Binding, ErlS, ExS, PruneBefore) ->
109112
%% If the variable is part of the pruning (usually the input binding)
110113
%% and is unused, we removed it from vars.
111114
(Pair, Version, {B, V})
112-
when Version < PruneBefore, not is_map_key({Pair, Version}, Unused) ->
115+
when Version =< PruneBefore, not is_map_key({Pair, Version}, Unused) ->
113116
{B, maps:remove(Pair, V)};
114117

115118
({Var, Kind} = Pair, Version, {B, V}) when is_atom(Kind) ->

lib/elixir/src/elixir_expand.erl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ expand({Name, Meta, Kind}, S, #{context := match} = E) when is_atom(Name), is_at
307307
%% Variable was already overridden
308308
#{Pair := VarVersion} when VarVersion >= PrematchVersion ->
309309
maybe_warn_underscored_var_repeat(Meta, Name, Kind, E),
310-
NewUnused = var_used(Pair, VarVersion, Unused),
310+
NewUnused = var_used(Meta, Pair, VarVersion, Unused),
311311
Var = {Name, [{version, VarVersion} | Meta], Kind},
312312
{Var, S#elixir_ex{unused={NewUnused, Version}}, E};
313313

@@ -354,7 +354,7 @@ expand({Name, Meta, Kind}, S, E) when is_atom(Name), is_atom(Kind) ->
354354
{ok, PairVersion} ->
355355
maybe_warn_underscored_var_access(Meta, Name, Kind, E),
356356
Var = {Name, [{version, PairVersion} | Meta], Kind},
357-
{Var, S#elixir_ex{unused={var_used(Pair, PairVersion, Unused), Version}}, E};
357+
{Var, S#elixir_ex{unused={var_used(Meta, Pair, PairVersion, Unused), Version}}, E};
358358

359359
Error ->
360360
case lists:keyfind(if_undefined, 1, Meta) of
@@ -613,8 +613,11 @@ var_unused({_, Kind} = Pair, Meta, Version, Unused, Override) ->
613613
false -> Unused
614614
end.
615615

616-
var_used({_, Kind} = Pair, Version, Unused) ->
616+
var_used(Meta, {_, Kind} = Pair, Version, Unused) ->
617+
KeepUnused = lists:keymember(keep_unused, 1, Meta),
618+
617619
if
620+
KeepUnused -> Unused;
618621
is_atom(Kind) -> Unused#{{Pair, Version} => false};
619622
true -> Unused
620623
end.

lib/elixir/src/elixir_module.erl

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-module(elixir_module).
22
-export([file/1, data_tables/1, is_open/1, mode/1, delete_definition_attributes/6,
3-
compile/4, expand_callback/6, format_error/1, compiler_modules/0,
3+
compile/5, expand_callback/6, format_error/1, compiler_modules/0,
44
write_cache/3, read_cache/2, next_counter/1]).
55
-include("elixir.hrl").
66
-define(counter_attr, {elixir, counter}).
@@ -64,7 +64,7 @@ next_counter(Module) ->
6464

6565
%% Compilation hook
6666

67-
compile(Module, Block, Vars, Env) when is_atom(Module) ->
67+
compile(Module, Block, Vars, Prune, Env) when is_atom(Module) ->
6868
#{line := Line, function := Function, versioned_vars := OldVerVars} = Env,
6969

7070
{VerVars, _} =
@@ -82,16 +82,16 @@ compile(Module, Block, Vars, Env) when is_atom(Module) ->
8282
#{lexical_tracker := nil} ->
8383
elixir_lexical:run(
8484
MaybeLexEnv,
85-
fun(LexEnv) -> compile(Line, Module, Block, Vars, LexEnv) end,
85+
fun(LexEnv) -> compile(Line, Module, Block, Vars, Prune, LexEnv) end,
8686
fun(_LexEnv) -> ok end
8787
);
8888
_ ->
89-
compile(Line, Module, Block, Vars, MaybeLexEnv)
89+
compile(Line, Module, Block, Vars, Prune, MaybeLexEnv)
9090
end;
91-
compile(Module, _Block, _Vars, #{line := Line, file := File}) ->
91+
compile(Module, _Block, _Vars, _Prune, #{line := Line, file := File}) ->
9292
elixir_errors:form_error([{line, Line}], File, ?MODULE, {invalid_module, Module}).
9393

94-
compile(Line, Module, Block, Vars, E) ->
94+
compile(Line, Module, Block, Vars, Prune, E) ->
9595
File = ?key(E, file),
9696
check_module_availability(Line, File, Module),
9797
ModuleAsCharlist = validate_module_name(Line, File, Module),
@@ -102,7 +102,7 @@ compile(Line, Module, Block, Vars, E) ->
102102

103103
try
104104
put_compiler_modules([Module | CompilerModules]),
105-
{Result, NE} = eval_form(Line, Module, DataBag, Block, Vars, E),
105+
{Result, ModuleE, CallbackE} = eval_form(Line, Module, DataBag, Block, Vars, Prune, E),
106106
CheckerInfo = checker_info(),
107107

108108
{Binary, PersistedAttributes, Autoload} =
@@ -133,7 +133,7 @@ compile(Line, Module, Block, Vars, E) ->
133133
CompileOpts = validate_compile_opts(RawCompileOpts, AllDefinitions, Unreachable, File, Line),
134134

135135
AfterVerify = bag_lookup_element(DataBag, {accumulate, after_verify}, 2),
136-
[elixir_env:trace({remote_function, [], VerifyMod, VerifyFun, 1}, E) ||
136+
[elixir_env:trace({remote_function, [], VerifyMod, VerifyFun, 1}, CallbackE) ||
137137
{VerifyMod, VerifyFun} <- AfterVerify],
138138

139139
ModuleMap = #{
@@ -158,8 +158,8 @@ compile(Line, Module, Block, Vars, E) ->
158158
end),
159159

160160
Autoload andalso code:load_binary(Module, beam_location(ModuleAsCharlist), Binary),
161-
eval_callbacks(Line, DataBag, after_compile, [NE, Binary], NE),
162-
elixir_env:trace({on_module, Binary, none}, E),
161+
eval_callbacks(Line, DataBag, after_compile, [CallbackE, Binary], CallbackE),
162+
elixir_env:trace({on_module, Binary, none}, ModuleE),
163163
warn_unused_attributes(File, DataSet, DataBag, PersistedAttributes),
164164
make_module_available(Module, Binary),
165165
(CheckerInfo == undefined) andalso
@@ -375,13 +375,27 @@ build(Line, File, Module) ->
375375

376376
%% Handles module and callback evaluations.
377377

378-
eval_form(Line, Module, DataBag, Block, Vars, E) ->
379-
{Value, EE} = elixir_compiler:compile(Block, Vars, E),
378+
eval_form(Line, Module, DataBag, Block, Vars, Prune, E) ->
379+
{Value, ExS, EE} = elixir_compiler:compile(Block, Vars, E),
380380
elixir_overridable:store_not_overridden(Module),
381381
EV = (elixir_env:reset_vars(EE))#{line := Line},
382382
EC = eval_callbacks(Line, DataBag, before_compile, [EV], EV),
383383
elixir_overridable:store_not_overridden(Module),
384-
{Value, EC}.
384+
{Value, maybe_prune_versioned_vars(Prune, Vars, ExS, E), EC}.
385+
386+
maybe_prune_versioned_vars(false, _Vars, _Exs, E) ->
387+
E;
388+
maybe_prune_versioned_vars(true, Vars, ExS, E) ->
389+
PruneBefore = length(Vars),
390+
#elixir_ex{vars={ExVars, _}, unused={Unused, _}} = ExS,
391+
392+
VersionedVars =
393+
maps:filter(fun
394+
(Pair, Version) when Version < PruneBefore, not is_map_key({Pair, Version}, Unused) -> false;
395+
(_, _) -> true
396+
end, ExVars),
397+
398+
E#{versioned_vars := VersionedVars}.
385399

386400
eval_callbacks(Line, DataBag, Name, Args, E) ->
387401
Callbacks = bag_lookup_element(DataBag, {accumulate, Name}, 2),

lib/elixir/test/elixir/code_test.exs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,34 @@ defmodule CodeTest do
206206
{[{{:x, :foo}, 2}], [x: :foo]}
207207
end
208208

209+
defmodule Tracer do
210+
def trace(event, env) do
211+
send(self(), {:trace, event, env})
212+
:ok
213+
end
214+
end
215+
216+
test "eval_quoted_with_env/3 with tracing and pruning" do
217+
env = %{Code.env_for_eval(__ENV__) | tracers: [Tracer], function: nil}
218+
binding = [x: 1, y: 2, z: 3]
219+
220+
quoted =
221+
quote do
222+
defmodule Elixir.CodeTest.TracingPruning do
223+
var!(y) = :updated
224+
var!(y)
225+
var!(x)
226+
end
227+
end
228+
229+
{_, binding, env} = Code.eval_quoted_with_env(quoted, binding, env, prune_binding: true)
230+
assert Enum.sort(binding) == []
231+
assert env.versioned_vars == %{}
232+
233+
assert_receive {:trace, {:on_module, _, _}, %{module: CodeTest.TracingPruning} = trace_env}
234+
assert trace_env.versioned_vars == %{{:result, Kernel} => 5, {:x, nil} => 1, {:y, nil} => 4}
235+
end
236+
209237
test "compile_file/1" do
210238
assert Code.compile_file(fixture_path("code_sample.exs")) == []
211239
refute fixture_path("code_sample.exs") in Code.required_files()

0 commit comments

Comments
 (0)