Skip to content

Commit bac5b1d

Browse files
committed
Add compile-time dependencies on require
Projects like Plug use require to establish compile time dependencies inside a Plug. The fact require only added a compile-time dependency in v1.13.0 was therefore a regression, addressed by this commit.
1 parent 39d8675 commit bac5b1d

File tree

9 files changed

+55
-37
lines changed

9 files changed

+55
-37
lines changed

lib/elixir/lib/kernel/lexical_tracker.ex

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ defmodule Kernel.LexicalTracker do
3030
end
3131

3232
@doc false
33-
def add_require(pid, module) when is_atom(module) do
34-
:gen_server.cast(pid, {:add_require, module})
33+
def add_export(pid, module) when is_atom(module) do
34+
:gen_server.cast(pid, {:add_export, module})
3535
end
3636

3737
@doc false
@@ -168,15 +168,16 @@ defmodule Kernel.LexicalTracker do
168168
{:noreply, update_in(state.compile_env, &:ordsets.add_element({app, path, return}, &1))}
169169
end
170170

171-
def handle_cast({:add_require, module}, state) do
171+
def handle_cast({:add_export, module}, state) do
172172
{:noreply, put_in(state.exports[module], true)}
173173
end
174174

175175
def handle_cast({:add_import, module, fas, line, warn}, state) do
176-
to_remove = for {{:import, {^module, _, _}} = key, _} <- state.directives, do: key
176+
%{directives: directives, exports: exports} = state
177+
to_remove = for {{:import, {^module, _, _}} = key, _} <- directives, do: key
177178

178179
directives =
179-
state.directives
180+
directives
180181
|> Map.drop(to_remove)
181182
|> add_directive(module, line, warn, :import)
182183

@@ -185,7 +186,7 @@ defmodule Kernel.LexicalTracker do
185186
add_directive(directives, {module, function, arity}, line, warn, :import)
186187
end)
187188

188-
{:noreply, %{state | directives: directives}}
189+
{:noreply, %{state | directives: directives, exports: Map.put(exports, module, true)}}
189190
end
190191

191192
def handle_cast({:add_alias, module, line, warn}, state) do
@@ -221,9 +222,9 @@ defmodule Kernel.LexicalTracker do
221222
do: Map.put(references, module, :compile)
222223

223224
defp add_reference(references, module, :runtime) when is_atom(module) do
224-
case Map.fetch(references, module) do
225-
{:ok, _} -> references
226-
:error -> Map.put(references, module, :runtime)
225+
case references do
226+
%{^module => _} -> references
227+
_ -> Map.put(references, module, :runtime)
227228
end
228229
end
229230

lib/elixir/lib/kernel/special_forms.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,9 @@ defmodule Kernel.SpecialForms do
554554
defmacro alias(module, opts), do: error!([module, opts])
555555

556556
@doc """
557-
Requires a module in order to use its macros.
557+
Requires a module as a compile-time dependency.
558+
559+
Requiring a module is necessary in order to use its macros.
558560
559561
## Examples
560562
@@ -576,7 +578,6 @@ defmodule Kernel.SpecialForms do
576578
577579
`require/2` also accepts `:as` as an option so it automatically sets
578580
up an alias. Please check `alias/2` for more information.
579-
580581
"""
581582
defmacro require(module, opts), do: error!([module, opts])
582583

lib/elixir/lib/module.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1811,7 +1811,8 @@ defmodule Module do
18111811
acc
18121812

18131813
true ->
1814-
:elixir_env.trace({:require, [], behaviour, []}, env)
1814+
event = {:remote_function, [], behaviour, :behaviour_info, 1}
1815+
:elixir_env.trace(event, %{env | function: {:__info__, 1}})
18151816
optional_callbacks = behaviour_info(behaviour, :optional_callbacks)
18161817
callbacks = behaviour_info(behaviour, :callbacks)
18171818
Enum.reduce(callbacks, acc, &add_callback(&1, behaviour, env, optional_callbacks, &2))

lib/elixir/src/elixir_expand.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ expand({require, Meta, [Ref, Opts]}, S, E) ->
8585
if
8686
is_atom(ERef) ->
8787
elixir_aliases:ensure_loaded(Meta, ERef, ET),
88+
elixir_env:trace({require, Meta, ERef, EOpts}, ET),
8889
{ERef, ST, expand_require(Meta, ERef, EOpts, ET)};
8990
true ->
9091
form_error(Meta, E, ?MODULE, {expected_compile_time_module, require, Ref})
@@ -931,7 +932,6 @@ no_alias_expansion(Other) ->
931932
Other.
932933

933934
expand_require(Meta, Ref, Opts, E) ->
934-
elixir_env:trace({require, Meta, Ref, Opts}, E),
935935
RE = E#{requires := ordsets:add_element(Ref, ?key(E, requires))},
936936
expand_alias(Meta, false, Ref, Opts, RE).
937937

lib/elixir/src/elixir_lexical.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ trace({alias_expansion, _Meta, Lookup, _Result}, #{lexical_tracker := Pid}) ->
4747
?tracker:alias_dispatch(Pid, Lookup),
4848
ok;
4949
trace({require, _Meta, Module, _Opts}, #{lexical_tracker := Pid}) ->
50-
?tracker:add_require(Pid, Module),
50+
?tracker:remote_dispatch(Pid, Module, compile),
5151
ok;
5252
trace({struct_expansion, _Meta, Module, _Keys}, #{lexical_tracker := Pid}) ->
53-
?tracker:add_require(Pid, Module),
53+
?tracker:add_export(Pid, Module),
5454
ok;
5555
trace({alias_reference, _Meta, Module}, #{lexical_tracker := Pid} = E) ->
5656
?tracker:remote_dispatch(Pid, Module, mode(E)),

lib/elixir/test/elixir/kernel/lexical_tracker_test.exs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ defmodule Kernel.LexicalTrackerTest do
2121
assert D.references(config[:pid]) == {[String], [], [], []}
2222
end
2323

24-
test "can add requires", config do
25-
D.add_require(config[:pid], URI)
24+
test "can add module requires and exports", config do
25+
D.add_export(config[:pid], URI)
2626
assert D.references(config[:pid]) == {[], [URI], [], []}
2727

2828
D.remote_dispatch(config[:pid], URI, :runtime)
@@ -33,7 +33,6 @@ defmodule Kernel.LexicalTrackerTest do
3333
end
3434

3535
test "can add module imports", config do
36-
D.add_require(config[:pid], String)
3736
D.add_import(config[:pid], String, [], 1, true)
3837

3938
D.import_dispatch(config[:pid], String, {:upcase, 1}, :runtime)
@@ -44,7 +43,7 @@ defmodule Kernel.LexicalTrackerTest do
4443
end
4544

4645
test "can add module with {function, arity} imports", config do
47-
D.add_require(config[:pid], String)
46+
D.add_export(config[:pid], String)
4847
D.add_import(config[:pid], String, [upcase: 1], 1, true)
4948

5049
D.import_dispatch(config[:pid], String, {:upcase, 1}, :compile)
@@ -181,18 +180,18 @@ defmodule Kernel.LexicalTrackerTest do
181180
{{compile, _exports, runtime, _}, _binding} =
182181
Code.eval_string("""
183182
defmodule Kernel.LexicalTrackerTest.Defdelegate do
184-
defdelegate a, to: A
183+
defdelegate parse(arg), to: URI
185184
186-
opts = [to: B]
187-
defdelegate b, opts
185+
opts = [to: Range]
186+
defdelegate disjoint?(left, right), opts
188187
189188
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
190189
end |> elem(3)
191190
""")
192191

193-
refute A in compile
194-
assert B in compile
195-
assert A in runtime
192+
refute URI in compile
193+
assert Range in compile
194+
assert URI in runtime
196195
end
197196

198197
test "imports adds an export dependency" do
@@ -209,6 +208,20 @@ defmodule Kernel.LexicalTrackerTest do
209208
refute String in runtime
210209
end
211210

211+
test "requires adds a compile dependency" do
212+
{{compile, exports, runtime, _}, _binding} =
213+
Code.eval_string("""
214+
defmodule Kernel.LexicalTrackerTest.Requires do
215+
require String
216+
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
217+
end |> elem(3)
218+
""")
219+
220+
assert String in compile
221+
refute String in exports
222+
refute String in runtime
223+
end
224+
212225
test "structs are exports or compile time" do
213226
{{compile, exports, runtime, _}, _binding} =
214227
Code.eval_string("""

lib/mix/lib/mix/tasks/xref.ex

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -510,13 +510,16 @@ defmodule Mix.Tasks.Xref do
510510

511511
@doc false
512512
def trace({:require, meta, module, _opts}, env),
513-
do: add_trace(:export, :require, module, module, meta, env)
513+
do: add_trace(:compile, :require, module, module, meta, env)
514+
515+
def trace({:import, meta, module, _opts}, env),
516+
do: add_trace(:export, :import, module, module, meta, env)
514517

515518
def trace({:struct_expansion, meta, module, _keys}, env),
516519
do: add_trace(:export, :struct, module, module, meta, env)
517520

518521
def trace({:alias_reference, meta, module}, env) when env.module != module,
519-
do: add_trace(mode(env), :alias, module, module, meta, env)
522+
do: add_trace(mode(env), :module, module, module, meta, env)
520523

521524
def trace({:remote_function, meta, module, function, arity}, env),
522525
do: add_trace(mode(env), :call, module, {module, function, arity}, meta, env)
@@ -551,14 +554,14 @@ defmodule Mix.Tasks.Xref do
551554
# We don't want to show aliases if there is an entry of the same type
552555
non_aliases =
553556
for {_file, _line, module_or_mfa, mode, type} <- entries,
554-
type != :alias,
557+
type != :module,
555558
into: %{},
556559
do: {{trace_module(module_or_mfa), mode}, []}
557560

558561
shell = Mix.shell()
559562

560563
for {file, line, module_or_mfa, mode, type} <- entries,
561-
type != :alias or not Map.has_key?(non_aliases, {module_or_mfa, mode}) do
564+
type != :module or not Map.has_key?(non_aliases, {module_or_mfa, mode}) do
562565
shell.info([
563566
Exception.format_file_line(Path.relative_to_cwd(file), line),
564567
?\s,

lib/mix/test/mix/tasks/xref_test.exs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ defmodule Mix.Tasks.XrefTest do
253253
output = """
254254
Compiling 2 files (.ex)
255255
Generated sample app
256-
lib/b.ex:2: require A (export)
256+
lib/b.ex:2: import A (export)
257257
lib/b.ex:3: call A.macro/0 (compile)
258258
lib/b.ex:4: import A.macro/0 (compile)
259259
lib/b.ex:5: call A.fun/0 (compile)
@@ -287,6 +287,7 @@ defmodule Mix.Tasks.XrefTest do
287287
output = """
288288
Compiling 2 files (.ex)
289289
Generated sample app
290+
lib/b.ex:2: require A (compile)
290291
lib/b.ex:3: call A.macro/0 (compile)
291292
"""
292293

@@ -313,10 +314,11 @@ defmodule Mix.Tasks.XrefTest do
313314
output = """
314315
Compiling 2 files (.ex)
315316
Generated sample app
317+
lib/b.ex:3: require A (compile)
316318
lib/b.ex:3: call A.macro/0 (compile)
317319
"""
318320

319-
message = "Too many traces (found: 1, permitted: 0)"
321+
message = "Too many traces (found: 2, permitted: 0)"
320322

321323
assert_raise Mix.Error, message, fn ->
322324
assert_trace(~w[--label compile --fail-above 0], "lib/b.ex", files, output)
@@ -759,10 +761,7 @@ defmodule Mix.Tasks.XrefTest do
759761
defmodule B do
760762
# Let's also test that we track literal atom behaviours
761763
@behaviour :"Elixir.A.Behaviour"
762-
763-
def foo do
764-
A.foo()
765-
end
764+
def foo, do: :foo
766765
end
767766
""")
768767

@@ -772,7 +771,7 @@ defmodule Mix.Tasks.XrefTest do
772771
digraph "xref graph" {
773772
"lib/a.ex"
774773
"lib/a.ex" -> "lib/b.ex" [label="(compile)"]
775-
"lib/b.ex" -> "lib/a.ex" [label="(export)"]
774+
"lib/b.ex" -> "lib/a.ex"
776775
"lib/b.ex"
777776
}
778777
"""

lib/mix/test/mix/umbrella_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ defmodule Mix.UmbrellaTest do
391391
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
392392

393393
# But exports dependencies are not recompiled
394-
File.write!("lib/bar.ex", "defmodule Bar, do: (require Foo)")
394+
File.write!("lib/bar.ex", "defmodule Bar, do: (import Foo, warn: false)")
395395

396396
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
397397
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}

0 commit comments

Comments
 (0)