summaryrefslogtreecommitdiff
path: root/lib/elixir/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib/elixir/lib')
-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
4 files changed, 94 insertions, 43 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