summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@dashbit.co>2023-05-16 11:52:59 +0200
committerJosé Valim <jose.valim@dashbit.co>2023-05-16 11:52:59 +0200
commita0f0f75ee86d01a7ccc7012a1e6cd3f019bd3d43 (patch)
tree873da86913bd03642d0d1048174362167c0e84b1
parent77c95d529bcb91b2e9190b1546ad60de6c0fb84b (diff)
downloadelixir-a0f0f75ee86d01a7ccc7012a1e6cd3f019bd3d43.tar.gz
Add Code.with_diagnostics/2, closes #12276
-rw-r--r--lib/elixir/lib/code.ex64
-rw-r--r--lib/elixir/lib/code/fragment.ex2
-rw-r--r--lib/elixir/lib/kernel/parallel_compiler.ex40
-rw-r--r--lib/elixir/lib/module/parallel_checker.ex31
-rw-r--r--lib/elixir/src/elixir.erl6
-rw-r--r--lib/elixir/src/elixir_errors.erl8
-rw-r--r--lib/elixir/src/elixir_module.erl13
-rw-r--r--lib/elixir/test/elixir/code_test.exs265
-rw-r--r--lib/mix/lib/mix/compilers/elixir.ex4
9 files changed, 293 insertions, 140 deletions
diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex
index f92da34e5..c5c5b63d2 100644
--- a/lib/elixir/lib/code.ex
+++ b/lib/elixir/lib/code.ex
@@ -194,6 +194,21 @@ defmodule Code do
"""
@type binding :: [{atom() | tuple(), any}]
+ @typedoc """
+ Diagnostics returned by the compiler and code evaluation.
+ """
+ @type diagnostic(severity) :: %{
+ file: Path.t(),
+ severity: severity,
+ message: String.t(),
+ position: position,
+ stacktrace: Exception.stacktrace()
+ }
+
+ @typedoc "The line. 0 indicates no line."
+ @type line() :: non_neg_integer()
+ @type position() :: line() | {pos_integer(), column :: non_neg_integer}
+
@boolean_compiler_options [
:docs,
:debug_info,
@@ -533,6 +548,51 @@ defmodule Code do
end)
end
+ @doc """
+ Executes the given `fun` and capture all diagnostics.
+
+ Diagnostics are warnings and errors emitted by the compiler
+ and by functions such as `IO.warn/2`.
+
+ ## Options
+
+ * `:log` - if the diagnostics should be logged as they happen.
+ Defaults to `false`.
+
+ """
+ @spec with_diagnostics(keyword(), (-> result)) :: {result, [diagnostic(:warning | :error)]}
+ when result: term()
+ def with_diagnostics(opts \\ [], fun) do
+ value = :erlang.get(:elixir_code_diagnostics)
+ log = Keyword.get(opts, :log, false)
+ :erlang.put(:elixir_code_diagnostics, {[], log})
+
+ try do
+ result = fun.()
+ {diagnostics, _log?} = :erlang.get(:elixir_code_diagnostics)
+ {result, Enum.reverse(diagnostics)}
+ after
+ if value == :undefined do
+ :erlang.erase(:elixir_code_diagnostics)
+ else
+ :erlang.put(:elixir_code_diagnostics, value)
+ end
+ end
+ end
+
+ @doc """
+ Prints a diagnostic into the standard error.
+
+ A diagnostic is either returned by `Kernel.ParallelCompiler`
+ or by `Code.with_diagnostics/2`.
+ """
+ @doc since: "1.15.0"
+ @spec print_diagnostic(diagnostic(:warning | :error)) :: :ok
+ def print_diagnostic(diagnostic) do
+ :elixir_errors.print_diagnostic(diagnostic)
+ :ok
+ end
+
@doc ~S"""
Formats the given code `string`.
@@ -876,7 +936,7 @@ defmodule Code do
warn_on_unnecessary_quotes: false,
literal_encoder: &{:ok, {:__block__, &2, [&1]}},
token_metadata: true,
- emit_warnings: false
+ warnings: false
] ++ opts
{forms, comments} = string_to_quoted_with_comments!(string, to_quoted_opts)
@@ -1688,7 +1748,7 @@ defmodule Code do
## Examples
- iex> Code.ensure_loaded?(Atom)
+ iex> Code.ensure_loaded?(String)
true
"""
diff --git a/lib/elixir/lib/code/fragment.ex b/lib/elixir/lib/code/fragment.ex
index 222694fd8..ed30dd3a4 100644
--- a/lib/elixir/lib/code/fragment.ex
+++ b/lib/elixir/lib/code/fragment.ex
@@ -1054,6 +1054,6 @@ defmodule Code.Fragment do
opts =
Keyword.take(opts, [:file, :line, :column, :columns, :token_metadata, :literal_encoder])
- Code.string_to_quoted(fragment, [cursor_completion: true, emit_warnings: false] ++ opts)
+ Code.string_to_quoted(fragment, [cursor_completion: true, warnings: false] ++ opts)
end
end
diff --git a/lib/elixir/lib/kernel/parallel_compiler.ex b/lib/elixir/lib/kernel/parallel_compiler.ex
index fc9a512d1..a8491220c 100644
--- a/lib/elixir/lib/kernel/parallel_compiler.ex
+++ b/lib/elixir/lib/kernel/parallel_compiler.ex
@@ -3,26 +3,14 @@ defmodule Kernel.ParallelCompiler do
A module responsible for compiling and requiring files in parallel.
"""
- @typedoc "The line. 0 indicates no line."
- @type line() :: non_neg_integer()
- @type position() :: line() | {pos_integer(), column :: non_neg_integer}
-
- @type diagnostic(severity) :: %{
- file: Path.t(),
- severity: severity,
- message: String.t(),
- position: position,
- stacktrace: Exception.stacktrace()
- }
-
@type info :: %{
- runtime_warnings: [diagnostic(:warning)],
- compile_warnings: [diagnostic(:warning)]
+ runtime_warnings: [Code.diagnostic(:warning)],
+ compile_warnings: [Code.diagnostic(:warning)]
}
# Deprecated types
- @type warning() :: {file :: Path.t(), position(), message :: String.t()}
- @type error() :: {file :: Path.t(), position(), message :: String.t()}
+ @type warning() :: {file :: Path.t(), Code.position(), message :: String.t()}
+ @type error() :: {file :: Path.t(), Code.position(), message :: String.t()}
@doc """
Starts a task for parallel compilation.
@@ -71,7 +59,7 @@ defmodule Kernel.ParallelCompiler do
resolved.
It returns `{:ok, modules, warnings}` or `{:error, errors, warnings}`
- by default but we recommend using `return_maps: true` so it returns
+ by default but we recommend using `return_diagnostics: true` so it returns
diagnostics as maps as well as a map of compilation information.
The map has the shape of:
@@ -115,14 +103,14 @@ defmodule Kernel.ParallelCompiler do
* `:beam_timestamp` - the modification timestamp to give all BEAM files
- * `:return_maps` (since v1.15.0) - returns maps with information instead of
+ * `:return_diagnostics` (since v1.15.0) - returns maps with information instead of
a list of warnings and returns diagnostics as maps instead of tuples
"""
@doc since: "1.6.0"
@spec compile([Path.t()], keyword()) ::
{:ok, [atom], [warning] | info()}
- | {:error, [error] | [diagnostic(:error)], [warning] | info()}
+ | {:error, [error] | [Code.diagnostic(:error)], [warning] | info()}
def compile(files, options \\ []) when is_list(options) do
spawn_workers(files, :compile, options)
end
@@ -135,7 +123,7 @@ defmodule Kernel.ParallelCompiler do
@doc since: "1.6.0"
@spec compile_to_path([Path.t()], Path.t(), keyword()) ::
{:ok, [atom], [warning] | info()}
- | {:error, [error] | [diagnostic(:error)], [warning] | info()}
+ | {:error, [error] | [Code.diagnostic(:error)], [warning] | info()}
def compile_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do
spawn_workers(files, {:compile, path}, options)
end
@@ -147,7 +135,7 @@ defmodule Kernel.ParallelCompiler do
automatically solved between files.
It returns `{:ok, modules, warnings}` or `{:error, errors, warnings}`
- by default but we recommend using `return_maps: true` so it returns
+ by default but we recommend using `return_diagnostics: true` so it returns
diagnostics as maps as well as a map of compilation information.
The map has the shape of:
@@ -172,14 +160,6 @@ defmodule Kernel.ParallelCompiler do
spawn_workers(files, :require, options)
end
- @doc """
- Prints a diagnostic returned by the compiler into stderr.
- """
- @doc since: "1.15.0"
- def print_diagnostic(diagnostic) do
- :elixir_errors.print_diagnostic(diagnostic)
- end
-
@doc false
# TODO: Deprecate me on Elixir v1.19
def print_warning({file, location, warning}) do
@@ -239,7 +219,7 @@ defmodule Kernel.ParallelCompiler do
end
# TODO: Require this to be set from Elixir v1.19
- if Keyword.get(options, :return_maps, false) do
+ if Keyword.get(options, :return_diagnostics, false) do
{status, modules_or_errors, info}
else
to_tuples = &Enum.map(&1, fn diag -> {diag.file, diag.position, diag.message} end)
diff --git a/lib/elixir/lib/module/parallel_checker.ex b/lib/elixir/lib/module/parallel_checker.ex
index 20f146931..f5e93a25f 100644
--- a/lib/elixir/lib/module/parallel_checker.ex
+++ b/lib/elixir/lib/module/parallel_checker.ex
@@ -48,7 +48,7 @@ defmodule Module.ParallelChecker do
@doc """
Spawns a process that runs the parallel checker.
"""
- def spawn({pid, checker}, module, info) do
+ def spawn({pid, checker}, module, info, log?) do
ref = make_ref()
spawned =
@@ -79,7 +79,7 @@ defmodule Module.ParallelChecker do
warnings =
if module_map do
- check_module(module_map, {checker, ets})
+ check_module(module_map, {checker, ets}, log?)
else
[]
end
@@ -143,12 +143,23 @@ defmodule Module.ParallelChecker do
"""
@spec verify(pid(), [{module(), Path.t()}]) :: [warning()]
def verify(checker, runtime_files) do
+ value = :erlang.get(:elixir_code_diagnostics)
+ log? = not match?({_, false}, value)
+
for {module, file} <- runtime_files do
- spawn({self(), checker}, module, file)
+ spawn({self(), checker}, module, file, log?)
end
count = :gen_server.call(checker, :start, :infinity)
- collect_results(count, [])
+ diagnostics = collect_results(count, [])
+
+ case :erlang.get(:elixir_code_diagnostics) do
+ :undefined -> :ok
+ {tail, true} -> :erlang.put(:elixir_code_diagnostics, {diagnostics ++ tail, true})
+ {tail, false} -> :erlang.put(:elixir_code_diagnostics, {diagnostics ++ tail, false})
+ end
+
+ diagnostics
end
defp collect_results(0, diagnostics) do
@@ -221,7 +232,7 @@ defmodule Module.ParallelChecker do
## Module checking
- defp check_module(module_map, cache) do
+ defp check_module(module_map, cache, log?) do
%{
module: module,
file: file,
@@ -252,7 +263,7 @@ defmodule Module.ParallelChecker do
|> Module.Types.warnings(file, definitions, no_warn_undefined, cache)
|> Kernel.++(behaviour_warnings)
|> group_warnings()
- |> emit_warnings()
+ |> emit_warnings(log?)
module_map
|> Map.get(:after_verify, [])
@@ -278,7 +289,7 @@ defmodule Module.ParallelChecker do
## Warning helpers
- def group_warnings(warnings) do
+ defp group_warnings(warnings) do
warnings
|> Enum.reduce(%{}, fn {module, warning, location}, acc ->
locations = MapSet.new([location])
@@ -288,11 +299,11 @@ defmodule Module.ParallelChecker do
|> Enum.sort()
end
- def emit_warnings(warnings) do
+ defp emit_warnings(warnings, log?) do
Enum.flat_map(warnings, fn {module, warning, locations} ->
message = module.format_warning(warning)
diagnostics = Enum.map(locations, &to_diagnostic(message, &1))
- :elixir_errors.print_warning([message, ?\n, format_stacktraces(diagnostics)])
+ log? and :elixir_errors.print_warning([message, ?\n, format_stacktraces(diagnostics)])
diagnostics
end)
end
@@ -317,7 +328,7 @@ defmodule Module.ParallelChecker do
severity: :warning,
file: file,
position: line,
- message: message,
+ message: IO.iodata_to_binary(message),
stacktrace: [to_stacktrace(file, line, mfa)]
}
end
diff --git a/lib/elixir/src/elixir.erl b/lib/elixir/src/elixir.erl
index 46bb07d2d..463f544e2 100644
--- a/lib/elixir/src/elixir.erl
+++ b/lib/elixir/src/elixir.erl
@@ -426,7 +426,7 @@ string_to_tokens(String, StartLine, StartColumn, File, Opts) when is_integer(Sta
{ok, _Line, _Column, [], Tokens} ->
{ok, Tokens};
{ok, _Line, _Column, Warnings, Tokens} ->
- (lists:keyfind(emit_warnings, 1, Opts) /= {emit_warnings, false}) andalso
+ (lists:keyfind(warnings, 1, Opts) /= {warnings, false}) andalso
[elixir_errors:erl_warn(L, File, M) || {L, M} <- lists:reverse(Warnings)],
{ok, Tokens};
{error, {Line, Column, {ErrorPrefix, ErrorSuffix}, Token}, _Rest, _Warnings, _SoFar} ->
@@ -486,8 +486,8 @@ to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom).
handle_parsing_opts(File, Opts) ->
WarningFile =
- case lists:keyfind(emit_warnings, 1, Opts) of
- {emit_warnings, false} -> nil;
+ case lists:keyfind(warnings, 1, Opts) of
+ {warnings, false} -> nil;
_ -> File
end,
LiteralEncoder =
diff --git a/lib/elixir/src/elixir_errors.erl b/lib/elixir/src/elixir_errors.erl
index 4a907f207..16874a1d6 100644
--- a/lib/elixir/src/elixir_errors.erl
+++ b/lib/elixir/src/elixir_errors.erl
@@ -33,7 +33,7 @@ print_diagnostic(#{severity := Severity, message := Message, stacktrace := Stack
[["\n ", 'Elixir.Exception':format_stacktrace_entry(E)] || E <- Stacktrace]
end,
io:put_chars(standard_error, [prefix(Severity), Message, Location, "\n\n"]),
- ok.
+ Diagnostic.
emit_diagnostic(Severity, Position, File, Message, Stacktrace) ->
Diagnostic = #{
@@ -44,7 +44,11 @@ emit_diagnostic(Severity, Position, File, Message, Stacktrace) ->
stacktrace => Stacktrace
},
- print_diagnostic(Diagnostic),
+ case get(elixir_code_diagnostics) of
+ undefined -> print_diagnostic(Diagnostic);
+ {Tail, true} -> put(elixir_code_diagnostics, {[print_diagnostic(Diagnostic) | Tail], true});
+ {Tail, false} -> put(elixir_code_diagnostics, {[Diagnostic | Tail], false})
+ end,
case get(elixir_compiler_info) of
undefined -> ok;
diff --git a/lib/elixir/src/elixir_module.erl b/lib/elixir/src/elixir_module.erl
index ca4bef4ed..88a66f14a 100644
--- a/lib/elixir/src/elixir_module.erl
+++ b/lib/elixir/src/elixir_module.erl
@@ -506,13 +506,20 @@ beam_location(ModuleAsCharlist) ->
checker_info() ->
case get(elixir_checker_info) of
undefined -> undefined;
- _ -> 'Elixir.Module.ParallelChecker':get()
+ _ ->
+ Log =
+ case erlang:get(elixir_code_diagnostics) of
+ {_, false} -> false;
+ _ -> true
+ end,
+
+ {'Elixir.Module.ParallelChecker':get(), Log}
end.
spawn_parallel_checker(undefined, _Module, _ModuleMap) ->
nil;
-spawn_parallel_checker(CheckerInfo, Module, ModuleMap) ->
- 'Elixir.Module.ParallelChecker':spawn(CheckerInfo, Module, ModuleMap).
+spawn_parallel_checker({CheckerInfo, Log}, Module, ModuleMap) ->
+ 'Elixir.Module.ParallelChecker':spawn(CheckerInfo, Module, ModuleMap, Log).
make_module_available(Module, Binary) ->
case get(elixir_module_binaries) of
diff --git a/lib/elixir/test/elixir/code_test.exs b/lib/elixir/test/elixir/code_test.exs
index 4b1e2e439..3e528d451 100644
--- a/lib/elixir/test/elixir/code_test.exs
+++ b/lib/elixir/test/elixir/code_test.exs
@@ -21,6 +21,41 @@ defmodule CodeTest do
Code.eval_quoted(contents, [], file: "sample.ex", line: 13)
+ describe "with_diagnostics/2" do
+ test "captures warnings" do
+ assert {:warn, [%{message: "hello"}]} =
+ Code.with_diagnostics(fn ->
+ IO.warn("hello")
+ :warn
+ end)
+ end
+
+ test "captures and logs warnings" do
+ assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
+ assert {:warn, [%{message: "hello"}]} =
+ Code.with_diagnostics([log: true], fn ->
+ IO.warn("hello")
+ :warn
+ end)
+ end) =~ "hello"
+ end
+
+ test "can be nested" do
+ assert {:warn, [%{message: "hello"}]} =
+ Code.with_diagnostics(fn ->
+ IO.warn("hello")
+
+ assert {:nested, [%{message: "world"}]} =
+ Code.with_diagnostics(fn ->
+ IO.warn("world")
+ :nested
+ end)
+
+ :warn
+ end)
+ end
+ end
+
describe "eval_string/1,2,3" do
test "correctly evaluates a string of code" do
assert Code.eval_string("1 + 2") == {3, []}
@@ -111,28 +146,45 @@ defmodule CodeTest do
end)
assert output =~ "incompatible types"
+ after
+ :code.purge(CodeTest.CheckerWarning)
+ :code.delete(CodeTest.CheckerWarning)
end
- end
- test "eval_quoted/1" do
- assert Code.eval_quoted(quote(do: 1 + 2)) == {3, []}
- assert CodeTest.Sample.eval_quoted_info() == {CodeTest.Sample, "sample.ex", 13}
+ test "captures checker diagnostics" do
+ {{{:module, _, _, _}, _}, diagnostics} =
+ Code.with_diagnostics(fn ->
+ Code.eval_string(File.read!(fixture_path("checker_warning.exs")), [])
+ end)
+
+ assert [%{message: "incompatible types:" <> _}] = diagnostics
+ after
+ :code.purge(CodeTest.CheckerWarning)
+ :code.delete(CodeTest.CheckerWarning)
+ end
end
- test "eval_quoted/2 with %Macro.Env{} at runtime" do
- alias :lists, as: MyList
- quoted = quote(do: MyList.flatten([[1, 2, 3]]))
+ describe "eval_quoted/1" do
+ test "evaluates expression" do
+ assert Code.eval_quoted(quote(do: 1 + 2)) == {3, []}
+ assert CodeTest.Sample.eval_quoted_info() == {CodeTest.Sample, "sample.ex", 13}
+ end
+
+ test "with %Macro.Env{} at runtime" do
+ alias :lists, as: MyList
+ quoted = quote(do: MyList.flatten([[1, 2, 3]]))
- assert Code.eval_quoted(quoted, [], __ENV__) == {[1, 2, 3], []}
+ assert Code.eval_quoted(quoted, [], __ENV__) == {[1, 2, 3], []}
- # Let's check it discards tracers since the lexical tracker is explicitly nil
- assert Code.eval_quoted(quoted, [], %{__ENV__ | tracers: [:bad]}) == {[1, 2, 3], []}
- end
+ # Let's check it discards tracers since the lexical tracker is explicitly nil
+ assert Code.eval_quoted(quoted, [], %{__ENV__ | tracers: [:bad]}) == {[1, 2, 3], []}
+ end
- test "eval_quoted/2 with %Macro.Env{} at compile time" do
- defmodule CompileTimeEnv do
- alias String.Chars
- {"foo", []} = Code.eval_string("Chars.to_string(:foo)", [], __ENV__)
+ test "with %Macro.Env{} at compile time" do
+ defmodule CompileTimeEnv do
+ alias String.Chars
+ {"foo", []} = Code.eval_string("Chars.to_string(:foo)", [], __ENV__)
+ end
end
end
@@ -144,101 +196,137 @@ defmodule CodeTest do
end
end
- test "eval_quoted_with_env/3" do
- alias :lists, as: MyList
- quoted = quote(do: MyList.flatten([[1, 2, 3]]))
- env = Code.env_for_eval(__ENV__)
- assert Code.eval_quoted_with_env(quoted, [], env) == {[1, 2, 3], [], env}
+ describe "eval_quoted_with_env/3" do
+ test "returns results, bindings, and env" do
+ alias :lists, as: MyList
+ quoted = quote(do: MyList.flatten([[1, 2, 3]]))
+ env = Code.env_for_eval(__ENV__)
+ assert Code.eval_quoted_with_env(quoted, [], env) == {[1, 2, 3], [], env}
- quoted = quote(do: alias(:dict, as: MyDict))
- {:dict, [], env} = Code.eval_quoted_with_env(quoted, [], env)
- assert Macro.Env.fetch_alias(env, :MyDict) == {:ok, :dict}
- end
+ quoted = quote(do: alias(:dict, as: MyDict))
+ {:dict, [], env} = Code.eval_quoted_with_env(quoted, [], env)
+ assert Macro.Env.fetch_alias(env, :MyDict) == {:ok, :dict}
+ end
- test "eval_quoted_with_env/3 with vars" do
- env = Code.env_for_eval(__ENV__)
- {1, [x: 1], env} = Code.eval_quoted_with_env(quote(do: var!(x) = 1), [], env)
- assert Macro.Env.vars(env) == [{:x, nil}]
- end
+ test "manages env vars" do
+ env = Code.env_for_eval(__ENV__)
+ {1, [x: 1], env} = Code.eval_quoted_with_env(quote(do: var!(x) = 1), [], env)
+ assert Macro.Env.vars(env) == [{:x, nil}]
+ end
- test "eval_quoted_with_env/3 with pruning" do
- env = Code.env_for_eval(__ENV__)
+ test "prunes vars" do
+ env = Code.env_for_eval(__ENV__)
- fun = fn quoted, binding ->
- {_, binding, env} = Code.eval_quoted_with_env(quoted, binding, env, prune_binding: true)
- {binding, Macro.Env.vars(env)}
- end
+ fun = fn quoted, binding ->
+ {_, binding, env} = Code.eval_quoted_with_env(quoted, binding, env, prune_binding: true)
+ {binding, Macro.Env.vars(env)}
+ end
- assert fun.(quote(do: 123), []) == {[], []}
- assert fun.(quote(do: 123), x: 2, y: 3) == {[], []}
+ assert fun.(quote(do: 123), []) == {[], []}
+ assert fun.(quote(do: 123), x: 2, y: 3) == {[], []}
- assert fun.(quote(do: var!(x) = 1), []) == {[x: 1], [x: nil]}
- assert fun.(quote(do: var!(x) = 1), x: 2, y: 3) == {[x: 1], [x: nil]}
+ assert fun.(quote(do: var!(x) = 1), []) == {[x: 1], [x: nil]}
+ assert fun.(quote(do: var!(x) = 1), x: 2, y: 3) == {[x: 1], [x: nil]}
- assert fun.(quote(do: var!(x, :foo) = 1), []) == {[{{:x, :foo}, 1}], [x: :foo]}
- assert fun.(quote(do: var!(x, :foo) = 1), x: 2, y: 3) == {[{{:x, :foo}, 1}], [x: :foo]}
+ assert fun.(quote(do: var!(x, :foo) = 1), []) == {[{{:x, :foo}, 1}], [x: :foo]}
+ assert fun.(quote(do: var!(x, :foo) = 1), x: 2, y: 3) == {[{{:x, :foo}, 1}], [x: :foo]}
- assert fun.(quote(do: var!(x, :foo) = 1), [{{:x, :foo}, 2}, {{:y, :foo}, 3}]) ==
- {[{{:x, :foo}, 1}], [x: :foo]}
+ assert fun.(quote(do: var!(x, :foo) = 1), [{{:x, :foo}, 2}, {{:y, :foo}, 3}]) ==
+ {[{{:x, :foo}, 1}], [x: :foo]}
- assert fun.(quote(do: fn -> var!(x, :foo) = 1 end), []) == {[], []}
- assert fun.(quote(do: fn -> var!(x, :foo) = 1 end), x: 1, y: 2) == {[], []}
+ assert fun.(quote(do: fn -> var!(x, :foo) = 1 end), []) == {[], []}
+ assert fun.(quote(do: fn -> var!(x, :foo) = 1 end), x: 1, y: 2) == {[], []}
- assert fun.(quote(do: fn -> var!(x) end), x: 2, y: 3) == {[x: 2], [x: nil]}
+ assert fun.(quote(do: fn -> var!(x) end), x: 2, y: 3) == {[x: 2], [x: nil]}
- assert fun.(quote(do: fn -> var!(x, :foo) end), [{{:x, :foo}, 2}, {{:y, :foo}, 3}]) ==
- {[{{:x, :foo}, 2}], [x: :foo]}
- end
+ assert fun.(quote(do: fn -> var!(x, :foo) end), [{{:x, :foo}, 2}, {{:y, :foo}, 3}]) ==
+ {[{{:x, :foo}, 2}], [x: :foo]}
+ end
- defmodule Tracer do
- def trace(event, env) do
- send(self(), {:trace, event, env})
- :ok
+ defmodule Tracer do
+ def trace(event, env) do
+ send(self(), {:trace, event, env})
+ :ok
+ end
end
- end
- test "eval_quoted_with_env/3 with tracing and pruning" do
- env = %{Code.env_for_eval(__ENV__) | tracers: [Tracer], function: nil}
- binding = [x: 1, y: 2, z: 3]
+ test "with tracing and pruning" do
+ env = %{Code.env_for_eval(__ENV__) | tracers: [Tracer], function: nil}
+ binding = [x: 1, y: 2, z: 3]
- quoted =
- quote do
- defmodule Elixir.CodeTest.TracingPruning do
- var!(y) = :updated
- var!(y)
- var!(x)
+ quoted =
+ quote do
+ defmodule Elixir.CodeTest.TracingPruning do
+ var!(y) = :updated
+ var!(y)
+ var!(x)
+ end
end
- end
- {_, binding, env} = Code.eval_quoted_with_env(quoted, binding, env, prune_binding: true)
- assert Enum.sort(binding) == []
- assert env.versioned_vars == %{}
+ {_, binding, env} = Code.eval_quoted_with_env(quoted, binding, env, prune_binding: true)
+ assert Enum.sort(binding) == []
+ assert env.versioned_vars == %{}
- assert_receive {:trace, {:on_module, _, _}, %{module: CodeTest.TracingPruning} = trace_env}
- assert trace_env.versioned_vars == %{{:result, Kernel} => 5, {:x, nil} => 1, {:y, nil} => 4}
- end
+ assert_receive {:trace, {:on_module, _, _}, %{module: CodeTest.TracingPruning} = trace_env}
+ assert trace_env.versioned_vars == %{{:result, Kernel} => 5, {:x, nil} => 1, {:y, nil} => 4}
+ end
- test "eval_quoted_with_env/3 with defguard" do
- require Integer
- env = Code.env_for_eval(__ENV__)
- quoted = quote do: Integer.is_even(1)
- {false, binding, env} = Code.eval_quoted_with_env(quoted, [], env, prune_binding: true)
- assert binding == []
- assert Macro.Env.vars(env) == []
+ test "with defguard" do
+ require Integer
+ env = Code.env_for_eval(__ENV__)
+ quoted = quote do: Integer.is_even(1)
+ {false, binding, env} = Code.eval_quoted_with_env(quoted, [], env, prune_binding: true)
+ assert binding == []
+ assert Macro.Env.vars(env) == []
+ end
end
- test "compile_file/1" do
- assert Code.compile_file(fixture_path("code_sample.exs")) == []
- refute fixture_path("code_sample.exs") in Code.required_files()
- end
+ describe "compile_file/1" do
+ test "compiles the given path" do
+ assert Code.compile_file(fixture_path("code_sample.exs")) == []
+ refute fixture_path("code_sample.exs") in Code.required_files()
+ end
+
+ test "emits checker warnings" do
+ output =
+ ExUnit.CaptureIO.capture_io(:stderr, fn ->
+ Code.compile_file(fixture_path("checker_warning.exs"))
+ end)
+
+ assert output =~ "incompatible types"
+ after
+ :code.purge(CodeTest.CheckerWarning)
+ :code.delete(CodeTest.CheckerWarning)
+ end
+
+ test "captures checker diagnostics" do
+ {[{CodeTest.CheckerWarning, _}], diagnostics} =
+ Code.with_diagnostics(fn ->
+ Code.compile_file(fixture_path("checker_warning.exs"))
+ end)
+
+ assert [%{message: "incompatible types:" <> _}] = diagnostics
+ after
+ :code.purge(CodeTest.CheckerWarning)
+ :code.delete(CodeTest.CheckerWarning)
+ end
- test "compile_file/1 also emits checker warnings" do
- output =
- ExUnit.CaptureIO.capture_io(:stderr, fn ->
- Code.compile_file(fixture_path("checker_warning.exs"))
- end)
+ test "captures checker diagnostics with logging" do
+ output =
+ ExUnit.CaptureIO.capture_io(:stderr, fn ->
+ {[{CodeTest.CheckerWarning, _}], diagnostics} =
+ Code.with_diagnostics([log: true], fn ->
+ Code.compile_file(fixture_path("checker_warning.exs"))
+ end)
+
+ assert [%{message: "incompatible types:" <> _}] = diagnostics
+ end)
- assert output =~ "incompatible types"
+ assert output =~ "incompatible types"
+ after
+ :code.purge(CodeTest.CheckerWarning)
+ :code.delete(CodeTest.CheckerWarning)
+ end
end
test "require_file/1" do
@@ -317,6 +405,9 @@ defmodule CodeTest do
end)
assert output =~ "incompatible types"
+ after
+ :code.purge(CodeTest.CheckerWarning)
+ :code.delete(CodeTest.CheckerWarning)
end
test "works across lexical scopes" do
diff --git a/lib/mix/lib/mix/compilers/elixir.ex b/lib/mix/lib/mix/compilers/elixir.ex
index c53d5f109..2bfd19ff2 100644
--- a/lib/mix/lib/mix/compilers/elixir.ex
+++ b/lib/mix/lib/mix/compilers/elixir.ex
@@ -709,7 +709,7 @@ defmodule Mix.Compilers.Elixir do
if print? do
Mix.shell().print_app()
- Kernel.ParallelCompiler.print_diagnostic(diagnostic)
+ Code.print_diagnostic(diagnostic)
end
diagnostic
@@ -957,7 +957,7 @@ defmodule Mix.Compilers.Elixir do
long_compilation_threshold: threshold,
profile: profile,
beam_timestamp: timestamp,
- return_maps: true
+ return_diagnostics: true
]
response = Kernel.ParallelCompiler.compile_to_path(stale, dest, compile_opts)