diff options
Diffstat (limited to 'lib/elixir/lib')
-rw-r--r-- | lib/elixir/lib/code.ex | 64 | ||||
-rw-r--r-- | lib/elixir/lib/code/fragment.ex | 2 | ||||
-rw-r--r-- | lib/elixir/lib/kernel/parallel_compiler.ex | 40 | ||||
-rw-r--r-- | lib/elixir/lib/module/parallel_checker.ex | 31 |
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 |