Skip to content

Commit fb19dec

Browse files
authored
Include lines in == Compilation error in file ... == slogans (#14538)
Closes #14535.
1 parent 1a8d13c commit fb19dec

File tree

9 files changed

+76
-52
lines changed

9 files changed

+76
-52
lines changed

lib/elixir/lib/code.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2078,7 +2078,7 @@ defmodule Code do
20782078
case :code.ensure_loaded(module) do
20792079
{:error, :nofile} = error ->
20802080
if can_await_module_compilation?() do
2081-
case Kernel.ErrorHandler.ensure_compiled(module, :module, mode) do
2081+
case Kernel.ErrorHandler.ensure_compiled(module, :module, mode, nil) do
20822082
:found -> {:module, module}
20832083
:deadlock -> {:error, :unavailable}
20842084
:not_found -> {:error, :nofile}

lib/elixir/lib/kernel/cli.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,9 @@ defmodule Kernel.CLI do
199199
" " <> String.replace(string, "\n", "\n ")
200200
end
201201

202-
@elixir_internals [:elixir, :elixir_aliases, :elixir_expand, :elixir_compiler, :elixir_module] ++
203-
[:elixir_clauses, :elixir_lexical, :elixir_def, :elixir_map] ++
202+
@elixir_internals [:elixir, :elixir_aliases, :elixir_clauses, :elixir_compiler, :elixir_def] ++
203+
[:elixir_def, :elixir_dispatch, :elixir_expand, :elixir_lexical] ++
204+
[:elixir_map, :elixir_module] ++
204205
[:elixir_erl, :elixir_erl_clauses, :elixir_erl_compiler, :elixir_erl_pass] ++
205206
[Kernel.ErrorHandler, Module.ParallelChecker]
206207

lib/elixir/lib/kernel/error_handler.ex

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ defmodule Kernel.ErrorHandler do
99

1010
@spec undefined_function(module, atom, list) :: term
1111
def undefined_function(module, fun, args) do
12-
ensure_loaded(module) or ensure_compiled(module, :module, :raise)
12+
ensure_loaded(module) or ensure_compiled(module, :module, :raise, nil)
1313
:error_handler.undefined_function(module, fun, args)
1414
end
1515

1616
@spec undefined_lambda(module, fun, list) :: term
1717
def undefined_lambda(module, fun, args) do
18-
ensure_loaded(module) or ensure_compiled(module, :module, :raise)
18+
ensure_loaded(module) or ensure_compiled(module, :module, :raise, nil)
1919
:error_handler.undefined_lambda(module, fun, args)
2020
end
2121

@@ -27,17 +27,22 @@ defmodule Kernel.ErrorHandler do
2727
end
2828
end
2929

30-
@spec ensure_compiled(module, atom, atom) :: :found | :not_found | :deadlock
30+
@spec ensure_compiled(module, atom, atom, nil | integer()) :: :found | :not_found | :deadlock
3131
# Never wait on nil because it should never be defined.
32-
def ensure_compiled(nil, _kind, _deadlock) do
32+
def ensure_compiled(nil, _kind, _deadlock, _position) do
3333
:not_found
3434
end
3535

36-
def ensure_compiled(module, kind, deadlock) do
36+
def ensure_compiled(module, kind, deadlock, position) do
3737
{compiler_pid, file_pid} = :erlang.get(:elixir_compiler_info)
3838
ref = :erlang.make_ref()
3939
modules = :elixir_module.compiler_modules()
40-
send(compiler_pid, {:waiting, kind, self(), ref, file_pid, module, modules, deadlock})
40+
41+
send(
42+
compiler_pid,
43+
{:waiting, kind, self(), ref, file_pid, position, module, modules, deadlock}
44+
)
45+
4146
:erlang.garbage_collect(self())
4247

4348
receive do

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ defmodule Kernel.ParallelCompiler do
8282
{compiler_pid, file_pid} = :erlang.get(:elixir_compiler_info)
8383
defining = :elixir_module.compiler_modules()
8484
on = Enum.map(refs_tasks, fn {_ref, %{pid: pid}} -> pid end)
85-
send(compiler_pid, {:waiting, :pmap, self(), ref, file_pid, on, defining, :raise})
85+
send(compiler_pid, {:waiting, :pmap, self(), ref, file_pid, nil, on, defining, :raise})
8686

8787
# Now we allow the tasks to run. This step is not strictly
8888
# necessary but it makes compilation more deterministic by
@@ -416,7 +416,7 @@ defmodule Kernel.ParallelCompiler do
416416
defp spawn_workers([{pid, found} | t], spawned, waiting, files, result, warnings, errors, state) do
417417
{files, waiting} =
418418
case Map.pop(waiting, pid) do
419-
{{kind, ref, file_pid, on, _defining, _deadlock}, waiting} ->
419+
{%{kind: kind, ref: ref, file_pid: file_pid, on: on}, waiting} ->
420420
send(pid, {ref, found})
421421
{update_timing(files, file_pid, {:waiting, kind, on}), waiting}
422422

@@ -607,22 +607,22 @@ defmodule Kernel.ParallelCompiler do
607607
defp without_definition(waiting_list, files) do
608608
nilify_empty_or_sort(
609609
for %{pid: file_pid} <- files,
610-
{pid, {_, _, ^file_pid, on, _, _}} <- waiting_list,
610+
{pid, %{file_pid: ^file_pid, on: on}} <- waiting_list,
611611
is_atom(on) and not defining?(on, waiting_list),
612612
do: {pid, :not_found}
613613
)
614614
end
615615

616616
defp deadlocked(waiting_list, type, defining?) do
617617
nilify_empty_or_sort(
618-
for {pid, {_, _, _, on, _, ^type}} <- waiting_list,
618+
for {pid, %{on: on, deadlock: ^type}} <- waiting_list,
619619
is_atom(on) and defining?(on, waiting_list) == defining?,
620620
do: {pid, :deadlock}
621621
)
622622
end
623623

624624
defp defining?(on, waiting_list) do
625-
Enum.any?(waiting_list, fn {_, {_, _, _, _, defining, _}} -> on in defining end)
625+
Enum.any?(waiting_list, fn {_, %{defining: defining}} -> on in defining end)
626626
end
627627

628628
defp nilify_empty_or_sort([]), do: nil
@@ -689,11 +689,12 @@ defmodule Kernel.ParallelCompiler do
689689
)
690690

691691
# If we are simply requiring files, we do not add to waiting.
692-
{:waiting, _kind, child, ref, _file_pid, _on, _defining, _deadlock} when output == :require ->
692+
{:waiting, _kind, child, ref, _file_pid, _position, _on, _defining, _deadlock}
693+
when output == :require ->
693694
send(child, {ref, :not_found})
694695
spawn_workers(queue, spawned, waiting, files, result, warnings, errors, state)
695696

696-
{:waiting, kind, child_pid, ref, file_pid, on, defining, deadlock} ->
697+
{:waiting, kind, child_pid, ref, file_pid, position, on, defining, deadlock} ->
697698
# If we already got what we were waiting for, do not put it on waiting.
698699
# If we're waiting on ourselves, send :found so that we can crash with
699700
# a better error.
@@ -706,7 +707,17 @@ defmodule Kernel.ParallelCompiler do
706707
send(child_pid, {ref, reply})
707708
{waiting, files, result}
708709
else
709-
waiting = Map.put(waiting, child_pid, {kind, ref, file_pid, on, defining, deadlock})
710+
waiting =
711+
Map.put(waiting, child_pid, %{
712+
kind: kind,
713+
ref: ref,
714+
file_pid: file_pid,
715+
position: position,
716+
on: on,
717+
defining: defining,
718+
deadlock: deadlock
719+
})
720+
710721
files = update_timing(files, file_pid, :compiling)
711722
result = Map.put(result, {kind, on}, [child_pid | available_or_pending])
712723
{waiting, files, result}
@@ -760,7 +771,7 @@ defmodule Kernel.ParallelCompiler do
760771
terminate(new_files)
761772

762773
return_error(warnings, errors, state, fn ->
763-
print_error(file, kind, reason, stack)
774+
print_error(file, nil, kind, reason, stack)
764775
[to_error(file, kind, reason, stack)]
765776
end)
766777

@@ -774,7 +785,7 @@ defmodule Kernel.ParallelCompiler do
774785
terminate(files)
775786

776787
return_error(warnings, errors, state, fn ->
777-
print_error(file.file, :exit, reason, [])
788+
print_error(file.file, nil, :exit, reason, [])
778789
[to_error(file.file, :exit, reason, [])]
779790
end)
780791
else
@@ -949,11 +960,11 @@ defmodule Kernel.ParallelCompiler do
949960
{:current_stacktrace, stacktrace} = Process.info(pid, :current_stacktrace)
950961
Process.exit(pid, :kill)
951962

952-
{kind, _, _, on, _, _} = Map.fetch!(waiting, pid)
963+
%{kind: kind, on: on, position: position} = Map.fetch!(waiting, pid)
953964
description = "deadlocked waiting on #{kind} #{inspect(on)}"
954965
error = CompileError.exception(description: description, file: nil, line: nil)
955-
print_error(file, :error, error, stacktrace)
956-
{Path.relative_to_cwd(file), on, description, stacktrace}
966+
print_error(file, position, :error, error, stacktrace)
967+
{Path.relative_to_cwd(file), position, on, description, stacktrace}
957968
end
958969

959970
IO.puts(:stderr, """
@@ -967,24 +978,25 @@ defmodule Kernel.ParallelCompiler do
967978
|> Enum.map(&(&1 |> elem(0) |> String.length()))
968979
|> Enum.max()
969980

970-
for {file, mod, _, _} <- deadlock do
981+
for {file, _, mod, _, _} <- deadlock do
971982
IO.puts(:stderr, [" ", String.pad_leading(file, max), " => " | inspect(mod)])
972983
end
973984

974985
IO.puts(
975986
:stderr,
976987
"\nEnsure there are no compile-time dependencies between those files " <>
977-
"and that the modules they reference exist and are correctly named\n"
988+
"(such as structs or macros) and that the modules they reference exist " <>
989+
"and are correctly named\n"
978990
)
979991

980-
for {file, _, description, stacktrace} <- deadlock do
992+
for {file, position, _, description, stacktrace} <- deadlock do
981993
file = Path.absname(file)
982994

983995
%{
984996
severity: :error,
985997
file: file,
986998
source: file,
987-
position: nil,
999+
position: position,
9881000
message: description,
9891001
stacktrace: stacktrace,
9901002
span: nil
@@ -1004,9 +1016,11 @@ defmodule Kernel.ParallelCompiler do
10041016
:ok
10051017
end
10061018

1007-
defp print_error(file, kind, reason, stack) do
1019+
defp print_error(file, position, kind, reason, stack) do
1020+
position = if is_integer(position), do: ":#{position}", else: ""
1021+
10081022
IO.write(:stderr, [
1009-
"\n== Compilation error in file #{Path.relative_to_cwd(file)} ==\n",
1023+
"\n== Compilation error in file #{Path.relative_to_cwd(file)}#{position} ==\n",
10101024
Kernel.CLI.format_error(kind, reason, stack)
10111025
])
10121026
end

lib/elixir/src/elixir.erl

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -209,16 +209,8 @@ env_for_eval(#{lexical_tracker := Pid} = Env) ->
209209
end;
210210
env_for_eval(Opts) when is_list(Opts) ->
211211
Env = elixir_env:new(),
212-
213-
Line = case lists:keyfind(line, 1, Opts) of
214-
{line, LineOpt} when is_integer(LineOpt) -> LineOpt;
215-
false -> ?key(Env, line)
216-
end,
217-
218-
File = case lists:keyfind(file, 1, Opts) of
219-
{file, FileOpt} when is_binary(FileOpt) -> FileOpt;
220-
false -> ?key(Env, file)
221-
end,
212+
Line = elixir_utils:get_line(Opts, Env),
213+
File = elixir_utils:get_file(Opts, Env),
222214

223215
Module = case lists:keyfind(module, 1, Opts) of
224216
{module, ModuleOpt} when is_atom(ModuleOpt) -> ModuleOpt;

lib/elixir/src/elixir_aliases.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ ensure_loaded(Meta, Module, E) ->
148148
ok;
149149

150150
_ ->
151-
case wait_for_module(Module) of
151+
case wait_for_module(Module, Meta, E) of
152152
found ->
153153
ok;
154154

@@ -163,10 +163,10 @@ ensure_loaded(Meta, Module, E) ->
163163
end
164164
end.
165165

166-
wait_for_module(Module) ->
166+
wait_for_module(Module, Meta, E) ->
167167
case erlang:get(elixir_compiler_info) of
168168
undefined -> not_found;
169-
_ -> 'Elixir.Kernel.ErrorHandler':ensure_compiled(Module, module, hard)
169+
_ -> 'Elixir.Kernel.ErrorHandler':ensure_compiled(Module, module, hard, elixir_utils:get_line(Meta, E))
170170
end.
171171

172172
%% Receives a list of atoms, binaries or lists

lib/elixir/src/elixir_map.erl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ load_struct_info(Meta, Name, Assocs, E) ->
137137

138138
maybe_load_struct_info(Meta, Name, Assocs, Trace, E) ->
139139
try
140-
case is_open(Name, E) andalso lookup_struct_info_from_data_tables(Name) of
140+
case is_open(Name, Meta, E) andalso lookup_struct_info_from_data_tables(Name) of
141141
%% If I am accessing myself and there is no attribute,
142142
%% don't invoke the fallback to avoid calling loaded code.
143143
false when ?key(E, module) =:= Name -> nil;
@@ -185,7 +185,7 @@ load_struct(Meta, Name, Assocs, E) ->
185185

186186
maybe_load_struct(Meta, Name, Assocs, E) ->
187187
try
188-
case is_open(Name, E) andalso elixir_def:external_for(Meta, Name, '__struct__', 1, [def]) of
188+
case is_open(Name, Meta, E) andalso elixir_def:external_for(Meta, Name, '__struct__', 1, [def]) of
189189
%% If I am accessing myself and there is no __struct__ function,
190190
%% don't invoke the fallback to avoid calling loaded code.
191191
false when ?key(E, module) =:= Name ->
@@ -232,17 +232,17 @@ assert_struct_assocs(Meta, Assocs, E) ->
232232
[function_error(Meta, E, ?MODULE, {invalid_key_for_struct, K})
233233
|| {K, _} <- Assocs, not is_atom(K)].
234234

235-
is_open(Name, E) ->
236-
in_context(Name, E) orelse ((code:ensure_loaded(Name) /= {module, Name}) andalso wait_for_struct(Name)).
235+
is_open(Name, Meta, E) ->
236+
in_context(Name, E) orelse ((code:ensure_loaded(Name) /= {module, Name}) andalso wait_for_struct(Name, Meta, E)).
237237

238238
in_context(Name, E) ->
239239
%% We also include the current module because it won't be present
240240
%% in context module in case the module name is defined dynamically.
241241
lists:member(Name, [?key(E, module) | ?key(E, context_modules)]).
242242

243-
wait_for_struct(Module) ->
243+
wait_for_struct(Module, Meta, E) ->
244244
(erlang:get(elixir_compiler_info) /= undefined) andalso
245-
('Elixir.Kernel.ErrorHandler':ensure_compiled(Module, struct, hard) =:= found).
245+
('Elixir.Kernel.ErrorHandler':ensure_compiled(Module, struct, hard, elixir_utils:get_line(Meta, E)) =:= found).
246246

247247
detail_undef(Name, E) ->
248248
case in_context(Name, E) andalso (?key(E, function) == nil) of

lib/elixir/src/elixir_utils.erl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
%% Convenience functions used throughout elixir source code
66
%% for ast manipulation and querying.
77
-module(elixir_utils).
8-
-export([get_line/1, generated/1,
8+
-export([get_line/1, get_line/2, get_file/2, generated/1,
99
split_last/1, split_opts/1, noop/0, var_context/2,
1010
characters_to_list/1, characters_to_binary/1, relative_to_cwd/1,
1111
macro_name/1, returns_boolean/1, caller/4, meta_keep/1,
@@ -176,6 +176,18 @@ get_line(Opts) when is_list(Opts) ->
176176
_ -> 0
177177
end.
178178

179+
get_line(Meta, Env) when is_list(Meta) ->
180+
case lists:keyfind(line, 1, Meta) of
181+
{line, LineOpt} when is_integer(LineOpt) -> LineOpt;
182+
false -> ?key(Env, line)
183+
end.
184+
185+
get_file(Meta, Env) when is_list(Meta) ->
186+
case lists:keyfind(file, 1, Meta) of
187+
{file, FileOpt} when is_binary(FileOpt) -> FileOpt;
188+
false -> ?key(Env, file)
189+
end.
190+
179191
generated([{generated, true} | _] = Meta) -> Meta;
180192
generated(Meta) -> [{generated, true} | Meta].
181193

lib/elixir/test/elixir/kernel/parallel_compiler_test.exs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ defmodule Kernel.ParallelCompilerTest do
380380
""",
381381
bar: """
382382
defmodule BarDeadlock do
383-
FooDeadlock.__info__(:module)
383+
%FooDeadlock{}
384384
end
385385
"""
386386
)
@@ -390,7 +390,7 @@ defmodule Kernel.ParallelCompilerTest do
390390
fixtures = [foo, bar]
391391
assert {:error, [bar_error, foo_error], @no_warnings} = compile(fixtures)
392392

393-
assert %{file: ^bar, position: nil, message: "deadlocked waiting on module FooDeadlock"} =
393+
assert %{file: ^bar, position: 2, message: "deadlocked waiting on struct FooDeadlock"} =
394394
bar_error
395395

396396
assert %{file: ^foo, position: nil, message: "deadlocked waiting on module BarDeadlock"} =
@@ -402,8 +402,8 @@ defmodule Kernel.ParallelCompilerTest do
402402
assert msg =~ "parallel_deadlock/bar.ex => FooDeadlock"
403403
assert msg =~ ~r"== Compilation error in file .+parallel_deadlock/foo\.ex =="
404404
assert msg =~ "** (CompileError) deadlocked waiting on module BarDeadlock"
405-
assert msg =~ ~r"== Compilation error in file .+parallel_deadlock/bar\.ex =="
406-
assert msg =~ "** (CompileError) deadlocked waiting on module FooDeadlock"
405+
assert msg =~ ~r"== Compilation error in file .+parallel_deadlock/bar\.ex:2 =="
406+
assert msg =~ "** (CompileError) deadlocked waiting on struct FooDeadlock"
407407
end
408408

409409
test "does not deadlock from Code.ensure_compiled" do

0 commit comments

Comments
 (0)