Do not include signatures twice in beam files
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
defmodule Module.ParallelChecker do
|
||||
@moduledoc false
|
||||
@elixir_checker_version :elixir_erl.checker_version()
|
||||
|
||||
import Kernel, except: [spawn: 3]
|
||||
|
||||
@@ -96,10 +97,12 @@ defmodule Module.ParallelChecker do
|
||||
end
|
||||
|
||||
with {:ok, binary} <- File.read(location),
|
||||
{:ok, {_, [debug_info: chunk]}} <- :beam_lib.chunks(binary, [:debug_info]),
|
||||
{:debug_info_v1, backend, data} = chunk,
|
||||
{:ok, module_map} <- backend.debug_info(:elixir_v1, module, data, []) do
|
||||
cache_from_module_map(table, module_map)
|
||||
{:ok,
|
||||
{_, [{:debug_info, {:debug_info_v1, backend, data}}, {~c"ExCk", checker}]}} <-
|
||||
:beam_lib.chunks(binary, [:debug_info, ~c"ExCk"]),
|
||||
{:ok, module_map} <- backend.debug_info(:elixir_v1, module, data, []),
|
||||
{@elixir_checker_version, contents} <- :erlang.binary_to_term(checker) do
|
||||
{cache_chunk(table, module, contents), module_map_to_module_tuple(module_map)}
|
||||
else
|
||||
_ -> {:not_found, nil}
|
||||
end
|
||||
@@ -177,9 +180,9 @@ defmodule Module.ParallelChecker do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives pairs of module maps and BEAM binaries. In parallel it verifies
|
||||
the modules and adds the ExCk chunk to the binaries. Returns the updated
|
||||
list of warnings from the verification.
|
||||
Receives pairs of module maps and BEAM binaries.
|
||||
|
||||
Returns the updated list of warnings from the verification.
|
||||
"""
|
||||
@spec verify(cache(), [{module(), Path.t()}]) :: [warning()]
|
||||
def verify({checker, table}, runtime_files) do
|
||||
@@ -423,7 +426,7 @@ defmodule Module.ParallelChecker do
|
||||
mode =
|
||||
with {^module, binary, _filename} <- object_code,
|
||||
{:ok, {^module, [{~c"ExCk", chunk}]}} <- :beam_lib.chunks(binary, [~c"ExCk"]),
|
||||
{:elixir_checker_v3, contents} <- :erlang.binary_to_term(chunk) do
|
||||
{@elixir_checker_version, contents} <- :erlang.binary_to_term(chunk) do
|
||||
# The chunk has more information, so that's our preference
|
||||
cache_chunk(table, module, contents)
|
||||
else
|
||||
|
||||
+46
-28
@@ -267,6 +267,8 @@ defmodule Protocol do
|
||||
|
||||
@optional_callbacks __deriving__: 2
|
||||
|
||||
@elixir_checker_version :elixir_erl.checker_version()
|
||||
|
||||
@doc false
|
||||
defmacro def(signature)
|
||||
|
||||
@@ -564,31 +566,34 @@ defmodule Protocol do
|
||||
# Ensure the types are sorted so the compiled beam is deterministic
|
||||
types = Enum.sort(types)
|
||||
|
||||
with {:ok, any, definitions, signatures, compile_info} <- beam_protocol(protocol),
|
||||
{:ok, definitions, signatures} <-
|
||||
consolidate(protocol, any, definitions, signatures, types),
|
||||
do: compile(definitions, signatures, compile_info)
|
||||
with {:ok, any, definitions, checker, compile_info} <- beam_protocol(protocol),
|
||||
{:ok, definitions, checker} <-
|
||||
consolidate(protocol, any, definitions, checker, types),
|
||||
do: compile(definitions, checker, compile_info)
|
||||
end
|
||||
|
||||
defp beam_protocol(protocol) do
|
||||
chunk_ids = [:debug_info, [?D, ?o, ?c, ?s]]
|
||||
chunk_ids = [:debug_info, [?E, ?x, ?C, ?k], [?D, ?o, ?c, ?s]]
|
||||
opts = [:allow_missing_chunks]
|
||||
|
||||
case :beam_lib.chunks(beam_file(protocol), chunk_ids, opts) do
|
||||
{:ok, {^protocol, [{:debug_info, debug_info} | chunks]}} ->
|
||||
{:ok, {^protocol, [{:debug_info, debug_info}, {_, checker} | chunks]}} ->
|
||||
{:debug_info_v1, _backend, {:elixir_v1, module_map, specs}} = debug_info
|
||||
%{attributes: attributes, definitions: definitions} = module_map
|
||||
|
||||
# Protocols in precompiled archives may not have signatures, so we default to an empty map.
|
||||
# TODO: Remove this on Elixir v1.23.
|
||||
signatures = Map.get(module_map, :signatures, %{})
|
||||
|
||||
chunks = :lists.filter(fn {_name, value} -> value != :missing_chunk end, chunks)
|
||||
chunks = :lists.map(fn {name, value} -> {List.to_string(name), value} end, chunks)
|
||||
|
||||
case attributes[:__protocol__] do
|
||||
[fallback_to_any: any] ->
|
||||
{:ok, any, definitions, signatures, {module_map, specs, chunks}}
|
||||
checker =
|
||||
with true <- is_binary(checker),
|
||||
{@elixir_checker_version, contents} <- :erlang.binary_to_term(checker) do
|
||||
contents
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
chunks = :lists.filter(fn {_name, value} -> value != :missing_chunk end, chunks)
|
||||
chunks = :lists.map(fn {name, value} -> {List.to_string(name), value} end, chunks)
|
||||
{:ok, any, definitions, checker, {module_map, specs, chunks}}
|
||||
|
||||
_ ->
|
||||
{:error, :not_a_protocol}
|
||||
@@ -607,7 +612,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Consolidate the protocol for faster implementations and fine-grained type information.
|
||||
defp consolidate(protocol, fallback_to_any?, definitions, signatures, types) do
|
||||
defp consolidate(protocol, fallback_to_any?, definitions, checker, types) do
|
||||
case List.keytake(definitions, {:__protocol__, 1}, 0) do
|
||||
{protocol_def, definitions} ->
|
||||
types = if fallback_to_any?, do: types, else: List.delete(types, Any)
|
||||
@@ -623,11 +628,24 @@ defmodule Protocol do
|
||||
protocol_def = change_protocol(protocol_def, types)
|
||||
impl_for = change_impl_for(impl_for, protocol, types)
|
||||
struct_impl_for = change_struct_impl_for(struct_impl_for, protocol, types, structs)
|
||||
new_signatures = new_signatures(definitions, protocol_funs, protocol, types)
|
||||
|
||||
definitions = [protocol_def, impl_for, impl_for!, struct_impl_for] ++ definitions
|
||||
signatures = Enum.into(new_signatures, signatures)
|
||||
{:ok, definitions, signatures}
|
||||
|
||||
checker =
|
||||
if checker do
|
||||
update_in(checker.exports, fn exports ->
|
||||
signatures = new_signatures(definitions, protocol_funs, protocol, types)
|
||||
|
||||
for {fun, info} <- exports do
|
||||
if sig = Map.get(signatures, fun) do
|
||||
{fun, %{info | sig: sig}}
|
||||
else
|
||||
{fun, info}
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
{:ok, definitions, checker}
|
||||
|
||||
nil ->
|
||||
{:error, :not_a_protocol}
|
||||
@@ -680,10 +698,12 @@ defmodule Protocol do
|
||||
{fun_arity, {:strong, nil, [{[domain | rest], Descr.dynamic()}]}}
|
||||
end
|
||||
|
||||
[
|
||||
{{:impl_for, 1}, {:strong, [Descr.term()], impl_for}},
|
||||
{{:impl_for!, 1}, {:strong, [domain], impl_for!}}
|
||||
] ++ new_signatures
|
||||
Map.new(
|
||||
[
|
||||
{{:impl_for, 1}, {:strong, [Descr.term()], impl_for}},
|
||||
{{:impl_for!, 1}, {:strong, [domain], impl_for!}}
|
||||
] ++ new_signatures
|
||||
)
|
||||
end
|
||||
|
||||
defp get_protocol_functions({_name, _kind, _meta, clauses}) do
|
||||
@@ -752,11 +772,9 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile(definitions, signatures, {module_map, specs, docs_chunk}) do
|
||||
# Protocols in precompiled archives may not have signatures, so we default to an empty map.
|
||||
# TODO: Remove this on Elixir v1.23.
|
||||
module_map = %{module_map | definitions: definitions} |> Map.put(:signatures, signatures)
|
||||
{:ok, :elixir_erl.consolidate(module_map, specs, docs_chunk)}
|
||||
defp compile(definitions, checker, {module_map, specs, docs_chunk}) do
|
||||
module_map = %{module_map | definitions: definitions}
|
||||
{:ok, :elixir_erl.consolidate(module_map, checker, specs, docs_chunk)}
|
||||
end
|
||||
|
||||
## Definition callbacks
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
%% Compiler backend to Erlang.
|
||||
|
||||
-module(elixir_erl).
|
||||
-export([elixir_to_erl/1, elixir_to_erl/2, definition_to_anonymous/5, compile/1, consolidate/3,
|
||||
get_ann/1, debug_info/4, scope/2, format_error/1]).
|
||||
-export([elixir_to_erl/1, elixir_to_erl/2, definition_to_anonymous/5, compile/1, consolidate/4,
|
||||
get_ann/1, debug_info/4, scope/2, checker_version/0, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-define(typespecs, 'Elixir.Kernel.Typespec').
|
||||
|
||||
checker_version() ->
|
||||
elixir_checker_v3.
|
||||
|
||||
%% debug_info callback
|
||||
|
||||
debug_info(elixir_v1, _Module, none, _Opts) ->
|
||||
@@ -132,9 +135,9 @@ scope(_Meta, ExpandCaptures) ->
|
||||
|
||||
%% Static compilation hook, used in protocol consolidation
|
||||
|
||||
consolidate(Map, TypeSpecs, DocsChunk) ->
|
||||
{Prefix, Forms, Def, _Defmacro, _Macros} = dynamic_form(Map, nil),
|
||||
CheckerChunk = checker_chunk(Map, Def, chunk_opts(Map)),
|
||||
consolidate(Map, Checker, TypeSpecs, DocsChunk) ->
|
||||
{Prefix, Forms, _Def, _Defmacro, _Macros} = dynamic_form(Map, nil),
|
||||
CheckerChunk = checker_chunk(Checker, chunk_opts(Map)),
|
||||
load_form(Map, Prefix, Forms, TypeSpecs, DocsChunk ++ CheckerChunk).
|
||||
|
||||
%% Dynamic compilation hook, used in regular compiler
|
||||
@@ -516,8 +519,10 @@ load_form(#{file := File, compile_opts := Opts} = Map, Prefix, Forms, Specs, Chu
|
||||
Binary.
|
||||
|
||||
debug_opts(Map, Specs, Opts) ->
|
||||
%% Signatures are moved to ExCk, no need to duplicate in chunks
|
||||
Keys = [signatures],
|
||||
case take_debug_opts(Opts) of
|
||||
{true, Rest} -> [{debug_info, {?MODULE, {elixir_v1, Map, Specs}}} | Rest];
|
||||
{true, Rest} -> [{debug_info, {?MODULE, {elixir_v1, maps:without(Keys, Map), Specs}}} | Rest];
|
||||
{false, Rest} -> [{debug_info, {?MODULE, none}} | Rest]
|
||||
end.
|
||||
|
||||
@@ -639,6 +644,11 @@ signature_to_binary(_, Name, Signature) ->
|
||||
Doc = 'Elixir.Inspect.Algebra':format('Elixir.Code':quoted_to_algebra(Quoted), infinity),
|
||||
'Elixir.IO':iodata_to_binary(Doc).
|
||||
|
||||
checker_chunk(nil, _ChunkOpts) ->
|
||||
[];
|
||||
checker_chunk(Contents, ChunkOpts) ->
|
||||
[{<<"ExCk">>, term_to_binary({checker_version(), Contents}, ChunkOpts)}].
|
||||
|
||||
checker_chunk(Map, Def, ChunkOpts) ->
|
||||
#{deprecated := Deprecated, defines_behaviour := DefinesBehaviour,
|
||||
signatures := Signatures, attributes := Attributes} = Map,
|
||||
@@ -662,7 +672,7 @@ checker_chunk(Map, Def, ChunkOpts) ->
|
||||
end
|
||||
},
|
||||
|
||||
[{<<"ExCk">>, term_to_binary({elixir_checker_v3, Contents}, ChunkOpts)}].
|
||||
checker_chunk(Contents, ChunkOpts).
|
||||
|
||||
prepend_behaviour_info(true, Def) -> [{{behaviour_info, 1}, []} | Def];
|
||||
prepend_behaviour_info(false, Def) -> Def.
|
||||
|
||||
@@ -23,10 +23,10 @@ defmodule Module.Types.InferTest do
|
||||
[]
|
||||
)
|
||||
|
||||
{:ok, {_, [debug_info: chunk]}} = :beam_lib.chunks(binary, [:debug_info])
|
||||
{:debug_info_v1, backend, data} = chunk
|
||||
{:ok, %{signatures: signatures}} = backend.debug_info(:elixir_v1, module, data, [])
|
||||
signatures
|
||||
version = :elixir_erl.checker_version()
|
||||
{:ok, {_, [{~c"ExCk", chunk}]}} = :beam_lib.chunks(binary, [~c"ExCk"])
|
||||
{^version, data} = :erlang.binary_to_term(chunk)
|
||||
for {fun, %{sig: sig}} <- data.exports, into: %{}, do: {fun, sig}
|
||||
end
|
||||
|
||||
test "infer types from patterns", config do
|
||||
|
||||
Reference in New Issue
Block a user