diff --git a/Makefile b/Makefile index 9efa58d4bd..b6f51b1ffb 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$hea SOURCE_DATE_EPOCH_PATH = lib/elixir/tmp/ebin_reproducible SOURCE_DATE_EPOCH_FILE = $(SOURCE_DATE_EPOCH_PATH)/SOURCE_DATE_EPOCH -.PHONY: install compile erlang elixir unicode app build_plt clean_plt dialyze test check_reproducible clean clean_residual_files format install_man clean_man docs Docs.zip Precompiled.zip zips +.PHONY: install install_man build_plt clean_plt dialyze test check_reproducible clean clean_elixir clean_man format docs Docs.zip Precompiled.zip zips .NOTPARALLEL: #==> Functions @@ -65,17 +65,21 @@ endef #==> Compilation tasks APP := lib/elixir/ebin/elixir.app +EEX := lib/eex/ebin/Elixir.EEx.beam +ELIXIR := lib/elixir/ebin/elixir.beam PARSER := lib/elixir/src/elixir_parser.erl KERNEL := lib/elixir/ebin/Elixir.Kernel.beam UNICODE := lib/elixir/ebin/Elixir.String.Unicode.beam default: compile -compile: erlang $(APP) elixir +compile: erlang elixir -erlang: $(PARSER) +erlang: $(ELIXIR) +$(ELIXIR): $(PARSER) lib/elixir/src/* $(Q) if [ ! -f $(APP) ]; then $(call CHECK_ERLANG_RELEASE); fi $(Q) cd lib/elixir && mkdir -p ebin && $(ERL_MAKE) + $(Q) $(GENERATE_APP) $(VERSION) $(PARSER): lib/elixir/src/elixir_parser.yrl $(Q) erlc -o $@ +'{verbose,true}' +'{report,true}' $< @@ -83,21 +87,20 @@ $(PARSER): lib/elixir/src/elixir_parser.yrl # Since Mix depends on EEx and EEx depends on Mix, # we first compile EEx without the .app file, # then Mix, and then compile EEx fully -elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit logger eex iex +elixir: stdlib $(EEX) mix ex_unit logger eex iex +stdlib: $(KERNEL) $(UNICODE) $(APP) -stdlib: lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex VERSION +$(KERNEL): lib/elixir/src/* lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex VERSION $(Q) if [ ! -f $(KERNEL) ]; then \ echo "==> bootstrap (compile)"; \ $(ERL) -s elixir_compiler bootstrap -s erlang halt; \ + "$(MAKE)" unicode; \ fi - $(Q) "$(MAKE)" unicode @ echo "==> elixir (compile)"; $(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/**/*.ex" -o ebin; - $(Q) "$(MAKE)" app -app: $(APP) $(APP): lib/elixir/src/elixir.app.src lib/elixir/ebin VERSION $(GENERATE_APP) - $(Q) $(GENERATE_APP) $< $@ $(VERSION) + $(Q) $(GENERATE_APP) $(VERSION) unicode: $(UNICODE) $(UNICODE): lib/elixir/unicode/* @@ -143,6 +146,7 @@ check_reproducible: compile $(Q) mv lib/iex/ebin/* lib/iex/tmp/ebin_reproducible/ $(Q) mv lib/logger/ebin/* lib/logger/tmp/ebin_reproducible/ $(Q) mv lib/mix/ebin/* lib/mix/tmp/ebin_reproducible/ + $(Q) rm -rf lib/*/ebin SOURCE_DATE_EPOCH=$(call READ_SOURCE_DATE_EPOCH) "$(MAKE)" compile $(Q) echo "Diffing..." $(Q) bin/elixir lib/elixir/scripts/diff.exs lib/elixir/ebin/ lib/elixir/tmp/ebin_reproducible/ @@ -153,16 +157,10 @@ check_reproducible: compile $(Q) bin/elixir lib/elixir/scripts/diff.exs lib/mix/ebin/ lib/mix/tmp/ebin_reproducible/ $(Q) echo "Builds are reproducible" -clean: +clean: clean_man rm -rf ebin rm -rf lib/*/ebin rm -rf $(PARSER) - $(Q) "$(MAKE)" clean_residual_files - -clean_elixir: - $(Q) rm -f lib/*/ebin/Elixir.*.beam - -clean_residual_files: rm -rf lib/*/_build/ rm -rf lib/*/tmp/ rm -rf lib/elixir/test/ebin/ @@ -172,7 +170,9 @@ clean_residual_files: rm -rf lib/mix/test/fixtures/git_sparse_repo/ rm -rf lib/mix/test/fixtures/archive/ebin/ rm -f erl_crash.dump - $(Q) "$(MAKE)" clean_man + +clean_elixir: + $(Q) rm -f lib/*/ebin/Elixir.*.beam #==> Documentation tasks diff --git a/lib/elixir/lib/kernel/parallel_compiler.ex b/lib/elixir/lib/kernel/parallel_compiler.ex index ee6809eaeb..db5a969a7c 100644 --- a/lib/elixir/lib/kernel/parallel_compiler.ex +++ b/lib/elixir/lib/kernel/parallel_compiler.ex @@ -31,11 +31,11 @@ defmodule Kernel.ParallelCompiler do dest = :erlang.get(:elixir_compiler_dest) {:error_handler, error_handler} = :erlang.process_info(self(), :error_handler) - {_parent, checker} = Module.ParallelChecker.get() + {_parent, cache} = Module.ParallelChecker.get() task = Task.async(fn -> - Module.ParallelChecker.put(compiler_pid, checker) + Module.ParallelChecker.put(compiler_pid, cache) :erlang.put(:elixir_compiler_info, {compiler_pid, file_pid}) :erlang.put(:elixir_compiler_file, file) dest != :undefined and :erlang.put(:elixir_compiler_dest, dest) @@ -253,11 +253,11 @@ defmodule Kernel.ParallelCompiler do max(:erlang.system_info(:schedulers_online), 2) end) - {:ok, checker} = Module.ParallelChecker.start_link(schedulers) + {:ok, cache} = Module.ParallelChecker.start_link(schedulers) {status, modules_or_errors, info} = try do - outcome = spawn_workers(schedulers, checker, files, output, options) + outcome = spawn_workers(schedulers, cache, files, output, options) {outcome, Keyword.get(options, :warnings_as_errors, false)} else {{:ok, _, %{runtime_warnings: r_warnings, compile_warnings: c_warnings} = info}, true} @@ -281,7 +281,7 @@ defmodule Kernel.ParallelCompiler do {{:error, errors, info}, _} -> {:error, errors, info} after - Module.ParallelChecker.stop(checker) + Module.ParallelChecker.stop(cache) end # TODO: Require this to be set from Elixir v1.19 @@ -428,13 +428,13 @@ defmodule Kernel.ParallelCompiler do end defp spawn_workers([file | queue], spawned, waiting, files, result, warnings, errors, state) do - %{output: output, dest: dest, checker: checker} = state + %{output: output, dest: dest, checker: cache} = state parent = self() file = Path.expand(file) {pid, ref} = :erlang.spawn_monitor(fn -> - Module.ParallelChecker.put(parent, checker) + Module.ParallelChecker.put(parent, cache) :erlang.put(:elixir_compiler_info, {parent, self()}) :erlang.put(:elixir_compiler_file, file) diff --git a/lib/elixir/lib/module/parallel_checker.ex b/lib/elixir/lib/module/parallel_checker.ex index 3fbab3b6c4..e42674ae36 100644 --- a/lib/elixir/lib/module/parallel_checker.ex +++ b/lib/elixir/lib/module/parallel_checker.ex @@ -11,13 +11,13 @@ defmodule Module.ParallelChecker do Initializes the parallel checker process. """ def start_link(schedulers \\ nil) do - :gen_server.start_link(__MODULE__, schedulers, []) + :proc_lib.start_link(__MODULE__, :init, [schedulers]) end @doc """ Stops the parallel checker process. """ - def stop(checker) do + def stop({checker, _table}) do send(checker, {__MODULE__, :stop}) :ok end @@ -28,31 +28,30 @@ defmodule Module.ParallelChecker do def get do case :erlang.get(:elixir_checker_info) do {parent, nil} -> - {:ok, checker} = start_link() - put(parent, checker) - {parent, checker} + {:ok, cache} = start_link() + put(parent, cache) + {parent, cache} - {parent, checker} -> - {parent, checker} + {parent, cache} -> + {parent, cache} end end @doc """ Stores the parallel checker information. """ - def put(pid, checker) when is_pid(pid) and is_pid(checker) do - :erlang.put(:elixir_checker_info, {pid, checker}) + def put(pid, {checker, table}) when is_pid(pid) do + :erlang.put(:elixir_checker_info, {pid, {checker, table}}) end @doc """ Spawns a process that runs the parallel checker. """ - def spawn({pid, checker}, module, module_map, log?) do - ets = :gen_server.call(checker, :ets, :infinity) - inner_spawn(pid, checker, module, cache_from_module_map(ets, module_map), log?) + def spawn({pid, {checker, table}}, module, module_map, log?) do + inner_spawn(pid, checker, table, module, cache_from_module_map(table, module_map), log?) end - defp inner_spawn(pid, checker, module, info, log?) do + defp inner_spawn(pid, checker, table, module, info, log?) do ref = make_ref() spawned = @@ -60,7 +59,7 @@ defmodule Module.ParallelChecker do mon_ref = Process.monitor(pid) receive do - {^ref, :cache, ets} -> + {^ref, :cache} -> Process.link(pid) module_tuple = @@ -73,7 +72,7 @@ defmodule Module.ParallelChecker do {: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(ets, module_map) + cache_from_module_map(table, module_map) else _ -> nil end @@ -88,7 +87,7 @@ defmodule Module.ParallelChecker do warnings = if module_tuple do - check_module(module_tuple, {checker, ets}, log?) + check_module(module_tuple, {checker, table}, log?) else [] end @@ -122,12 +121,12 @@ defmodule Module.ParallelChecker do case :erlang.get(:elixir_checker_info) do {_, nil} -> :ok - {_, checker} -> verify(checker, []) + {_, cache} -> verify(cache, []) end result after - {_, checker} = :erlang.get(:elixir_checker_info) + {_, cache} = :erlang.get(:elixir_checker_info) if previous != :undefined do :erlang.put(:elixir_checker_info, previous) @@ -135,7 +134,7 @@ defmodule Module.ParallelChecker do :erlang.erase(:elixir_checker_info) end - checker && stop(checker) + cache && stop(cache) end _ -> @@ -150,13 +149,13 @@ defmodule Module.ParallelChecker do the modules and adds the ExCk chunk to the binaries. Returns the updated list of warnings from the verification. """ - @spec verify(pid(), [{module(), Path.t()}]) :: [warning()] - def verify(checker, runtime_files) do + @spec verify(cache(), [{module(), Path.t()}]) :: [warning()] + def verify({checker, table}, runtime_files) do value = :erlang.get(:elixir_code_diagnostics) log? = not match?({_, false}, value) for {module, file} <- runtime_files do - inner_spawn(self(), checker, module, file, log?) + inner_spawn(self(), checker, table, module, file, log?) end count = :gen_server.call(checker, :start, :infinity) @@ -190,8 +189,8 @@ defmodule Module.ParallelChecker do Test cache. """ def test_cache do - {:ok, checker} = start_link() - {checker, :gen_server.call(checker, :ets, :infinity)} + {:ok, cache} = start_link() + cache end @doc """ @@ -202,17 +201,17 @@ defmodule Module.ParallelChecker do @spec fetch_export(cache(), module(), atom(), arity()) :: {:ok, mode(), binary() | nil, {:infer, [term()]} | :none} | {:error, :function | :module} - def fetch_export({server, ets}, module, fun, arity) do - case :ets.lookup(ets, module) do + def fetch_export({checker, table}, module, fun, arity) do + case :ets.lookup(table, module) do [] -> - cache_module({server, ets}, module) - fetch_export({server, ets}, module, fun, arity) + cache_module({checker, table}, module) + fetch_export({checker, table}, module, fun, arity) [{_key, false}] -> {:error, :module} [{_key, mode}] -> - case :ets.lookup(ets, {module, {fun, arity}}) do + case :ets.lookup(table, {module, {fun, arity}}) do [{_key, reason, signature}] -> {:ok, mode, reason, signature} [] -> {:error, :function} end @@ -371,15 +370,15 @@ defmodule Module.ParallelChecker do ## Cache - defp cache_module({server, ets}, module) do - if lock(server, module) do + defp cache_module({checker, table}, module) do + if lock(checker, module) do object_code = :code.get_object_code(module) # The chunk has more information, so that's our preference with {^module, binary, _filename} <- object_code, {:ok, {^module, [{~c"ExCk", chunk}]}} <- :beam_lib.chunks(binary, [~c"ExCk"]), {:elixir_checker_v1, contents} <- :erlang.binary_to_term(chunk) do - cache_chunk(ets, module, contents.exports) + cache_chunk(table, module, contents.exports) else _ -> # Otherwise, if the module is loaded, use its info @@ -387,21 +386,21 @@ defmodule Module.ParallelChecker do true -> {mode, exports} = info_exports(module) deprecated = info_deprecated(module) - cache_info(ets, module, exports, deprecated, %{}, mode) + cache_info(table, module, exports, deprecated, %{}, mode) false -> # Or load exports from chunk with {^module, binary, _filename} <- object_code, {:ok, {^module, [exports: exports]}} <- :beam_lib.chunks(binary, [:exports]) do - cache_info(ets, module, exports, %{}, %{}, :erlang) + cache_info(table, module, exports, %{}, %{}, :erlang) else _ -> - :ets.insert(ets, {module, false}) + :ets.insert(table, {module, false}) end end end - unlock(server, module) + unlock(checker, module) end end @@ -417,36 +416,36 @@ defmodule Module.ParallelChecker do _ -> %{} end - defp cache_from_module_map(ets, map) do + defp cache_from_module_map(table, map) do exports = [{:__info__, 1}] ++ behaviour_exports(map) ++ for({function, :def, _meta, _clauses} <- map.definitions, do: function) - cache_info(ets, map.module, exports, Map.new(map.deprecated), map.signatures, :elixir) + cache_info(table, map.module, exports, Map.new(map.deprecated), map.signatures, :elixir) module_map_to_module_tuple(map) end - defp cache_info(ets, module, exports, deprecated, sigs, mode) do + defp cache_info(table, module, exports, deprecated, sigs, mode) do Enum.each(exports, fn fa -> reason = Map.get(deprecated, fa) - :ets.insert(ets, {{module, fa}, reason, Map.get(sigs, fa, :none)}) + :ets.insert(table, {{module, fa}, reason, Map.get(sigs, fa, :none)}) end) - :ets.insert(ets, {module, mode}) + :ets.insert(table, {module, mode}) end - defp cache_chunk(ets, module, exports) do + defp cache_chunk(table, module, exports) do Enum.each(exports, fn {{fun, arity}, info} -> # TODO: Match on signature directly in Elixir v1.22+ :ets.insert( - ets, + table, {{module, {fun, arity}}, Map.get(info, :deprecated), Map.get(info, :sig, :none)} ) end) - :ets.insert(ets, {{module, {:__info__, 1}}, nil, :none}) - :ets.insert(ets, {module, :elixir}) + :ets.insert(table, {{module, {:__info__, 1}}, nil, :none}) + :ets.insert(table, {module, :elixir}) end defp behaviour_exports(%{defines_behaviour: true}), do: [{:behaviour_info, 1}] @@ -475,22 +474,22 @@ defmodule Module.ParallelChecker do ## Server callbacks def init(schedulers) do - ets = :ets.new(__MODULE__, [:set, :public, {:read_concurrency, true}]) + table = :ets.new(__MODULE__, [:set, :public, {:read_concurrency, true}]) + :proc_lib.init_ack({:ok, {self(), table}}) state = %{ - ets: ets, waiting: %{}, modules: [], spawned: 0, schedulers: schedulers || max(:erlang.system_info(:schedulers_online), 2) } - {:ok, state} + :gen_server.enter_loop(__MODULE__, [], state) end - def handle_call(:start, _from, %{ets: ets, modules: modules} = state) do + def handle_call(:start, _from, %{modules: modules} = state) do for {pid, ref} <- modules do - send(pid, {ref, :cache, ets}) + send(pid, {ref, :cache}) end for {_pid, ref} <- modules do @@ -502,10 +501,6 @@ defmodule Module.ParallelChecker do {:reply, length(modules), run_checkers(state)} end - def handle_call(:ets, _from, state) do - {:reply, state.ets, state} - end - def handle_call({:lock, module}, from, %{waiting: waiting} = state) do case waiting do %{^module => froms} -> diff --git a/lib/elixir/lib/module/types.ex b/lib/elixir/lib/module/types.ex index 898f546a8d..efead6b04b 100644 --- a/lib/elixir/lib/module/types.ex +++ b/lib/elixir/lib/module/types.ex @@ -33,8 +33,8 @@ defmodule Module.Types do @no_infer [__protocol__: 1, behaviour_info: 1] @doc false - def infer(module, file, defs, private, used_private, env) do - infer_signatures? = :elixir_config.get(:infer_signatures) + def infer(module, file, defs, private, used_private, env, {_, cache}) do + infer_signatures? = :elixir_config.get(:infer_signatures) and cache != nil finder = fn fun_arity -> @@ -60,7 +60,7 @@ defmodule Module.Types do end end - stack = stack(:infer, file, module, {:__info__, 1}, :all, env, handler) + stack = stack(:infer, file, module, {:__info__, 1}, env, cache, handler) {types, %{local_sigs: reachable_sigs} = context} = for {fun_arity, kind, meta, _clauses} = def <- defs, @@ -316,9 +316,9 @@ defmodule Module.Types do module: module, # Current function function: function, - # List of calls to not warn on as undefined or :all + # List of calls to not warn on as undefined or :all or Macro.Env indicating limited remotes no_warn_undefined: no_warn_undefined, - # A tuple with cache information or a Macro.Env struct indicating no remote traversals + # A tuple with cache information (may be nil) cache: cache, # The mode to be used, see the @modes attribute mode: mode, diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index 5c6312b841..f5cd6b49fe 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -429,6 +429,26 @@ defmodule Module.Types.Apply do end end + @doc """ + Returns the type of a remote capture. + """ + def remote_capture(modules, fun, arity, meta, stack, context) do + # TODO: We cannot return the unions of functions. Do we forbid this? + # Do we check it is always the same return type? Do we simply say it is a function? + if stack.mode == :traversal do + {dynamic(fun()), context} + else + context = + Enum.reduce( + modules, + context, + &(signature(&1, fun, arity, meta, stack, &2) |> elem(1)) + ) + + {dynamic(fun()), context} + end + end + @doc """ Gets a mfa signature. @@ -461,25 +481,30 @@ defmodule Module.Types.Apply do {signature(:module_info, arity), context} end - defp export(_module, _fun, _arity, _meta, %{cache: %Macro.Env{}}, context) do - {:none, context} - end - - defp export(module, fun, arity, meta, stack, context) do - case ParallelChecker.fetch_export(stack.cache, module, fun, arity) do - {:ok, mode, reason, info} -> - info = if info == :none, do: signature(fun, arity), else: info - {info, check_deprecated(mode, module, fun, arity, reason, meta, stack, context)} - - {:error, type} -> - context = - if warn_undefined?(module, fun, arity, stack) do - warn(__MODULE__, {:undefined, type, module, fun, arity}, meta, stack, context) - else - context - end - + defp export(module, fun, arity, meta, %{cache: cache} = stack, context) do + cond do + cache == nil or stack.mode == :traversal -> {:none, context} + + stack.mode == :infer and not builtin_module?(module) -> + {:none, context} + + true -> + case ParallelChecker.fetch_export(stack.cache, module, fun, arity) do + {:ok, mode, reason, info} -> + info = if info == :none, do: signature(fun, arity), else: info + {info, check_deprecated(mode, module, fun, arity, reason, meta, stack, context)} + + {:error, type} -> + context = + if warn_undefined?(module, fun, arity, stack) do + warn(__MODULE__, {:undefined, type, module, fun, arity}, meta, stack, context) + else + context + end + + {:none, context} + end end end @@ -507,6 +532,27 @@ defmodule Module.Types.Apply do end end + defp builtin_module?(module) do + is_map_key(builtin_modules(), module) + end + + defp builtin_modules do + case :persistent_term.get(__MODULE__, nil) do + nil -> + {:ok, mods} = :application.get_key(:elixir, :modules) + mods = Map.from_keys(mods, []) + :persistent_term.put(__MODULE__, mods) + mods + + %{} = mods -> + mods + end + end + + defp warn_undefined?(_, _, _, %{no_warn_undefined: %Macro.Env{}}) do + false + end + defp warn_undefined?(_, _, _, %{no_warn_undefined: :all}) do false end @@ -572,14 +618,14 @@ defmodule Module.Types.Apply do {dynamic(fun()), context} {_kind, _info, context} when stack.mode == :traversal -> - {fun(), context} + {dynamic(fun()), context} {kind, _info, context} -> if stack.mode != :infer and kind == :defp do # Mark all clauses as used, as the function is being exported. - {fun(), put_in(context.local_used[fun_arity], [])} + {dynamic(fun()), put_in(context.local_used[fun_arity], [])} else - {fun(), context} + {dynamic(fun()), context} end end end diff --git a/lib/elixir/lib/module/types/expr.ex b/lib/elixir/lib/module/types/expr.ex index 5bc129a7ee..7aab48d6fc 100644 --- a/lib/elixir/lib/module/types/expr.ex +++ b/lib/elixir/lib/module/types/expr.ex @@ -401,19 +401,8 @@ defmodule Module.Types.Expr do ) when is_atom(name) and is_integer(arity) do {remote_type, context} = of_expr(remote, stack, context) - - # TODO: We cannot return the unions of functions. Do we forbid this? - # Do we check it is always the same return type? Do we simply say it is a function? {mods, context} = Of.modules(remote_type, name, arity, expr, meta, stack, context) - - context = - Enum.reduce( - mods, - context, - &(Apply.signature(&1, name, arity, meta, stack, &2) |> elem(1)) - ) - - {dynamic(fun()), context} + Apply.remote_capture(mods, name, arity, meta, stack, context) end # TODO: &foo/1 diff --git a/lib/elixir/lib/module/types/of.ex b/lib/elixir/lib/module/types/of.ex index f477316c7f..d04b53151f 100644 --- a/lib/elixir/lib/module/types/of.ex +++ b/lib/elixir/lib/module/types/of.ex @@ -216,7 +216,7 @@ defmodule Module.Types.Of do Returns `__info__(:struct)` information about a struct. """ def struct_info(struct, meta, stack, context) do - case stack.cache do + case stack.no_warn_undefined do %Macro.Env{} = env -> case :elixir_map.maybe_load_struct_info(meta, struct, [], false, env) do {:ok, info} -> {info, context} diff --git a/lib/elixir/scripts/diff.exs b/lib/elixir/scripts/diff.exs index 53e688632e..f8c8b0f2c5 100644 --- a/lib/elixir/scripts/diff.exs +++ b/lib/elixir/scripts/diff.exs @@ -125,7 +125,7 @@ defmodule Diff do chunk1 != chunk2 do tmp_file1 = chunk1 |> formatter.() |> write_tmp() tmp_file2 = chunk2 |> formatter.() |> write_tmp() - [name1, ?\n, file_diff(tmp_file1, tmp_file2)] + [to_string(name1), ?\n, file_diff(tmp_file1, tmp_file2)] end end else diff --git a/lib/elixir/scripts/generate_app.escript b/lib/elixir/scripts/generate_app.escript index 256497380e..563f05f55c 100755 --- a/lib/elixir/scripts/generate_app.escript +++ b/lib/elixir/scripts/generate_app.escript @@ -1,7 +1,9 @@ #!/usr/bin/env escript %% -*- erlang -*- -main([Source, Target, Version]) -> +main([Version]) -> + Source = "lib/elixir/src/elixir.app.src", + Target = "lib/elixir/ebin/elixir.app", {ok, [{application, Name, Props0}]} = file:consult(Source), Ebin = filename:dirname(Target), Files = filelib:wildcard(filename:join(Ebin, "*.beam")), diff --git a/lib/elixir/src/elixir_module.erl b/lib/elixir/src/elixir_module.erl index fa8cde3e4d..4725cfd2a7 100644 --- a/lib/elixir/src/elixir_module.erl +++ b/lib/elixir/src/elixir_module.erl @@ -188,7 +188,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) -> true -> {#{}, []}; false -> UsedPrivate = bag_lookup_element(DataBag, used_private, 2), - 'Elixir.Module.Types':infer(Module, File, AllDefinitions, Private, UsedPrivate, E) + 'Elixir.Module.Types':infer(Module, File, AllDefinitions, Private, UsedPrivate, E, CheckerInfo) end, RawCompileOpts = bag_lookup_element(DataBag, {accumulate, compile}, 2), @@ -225,7 +225,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) -> elixir_env:trace({on_module, Binary, none}, ModuleE), warn_unused_attributes(DataSet, DataBag, PersistedAttributes, E), make_module_available(Module, Binary), - (CheckerInfo == undefined) andalso + (element(2, CheckerInfo) == nil) andalso [VerifyMod:VerifyFun(Module) || {VerifyMod, VerifyFun} <- bag_lookup_element(DataBag, {accumulate, after_verify}, 2)], {module, Module, Binary, Result} @@ -553,11 +553,11 @@ beam_location(ModuleAsCharlist) -> checker_info() -> case get(elixir_checker_info) of - undefined -> undefined; + undefined -> {self(), nil}; _ -> 'Elixir.Module.ParallelChecker':get() end. -spawn_parallel_checker(undefined, _Module, _ModuleMap) -> +spawn_parallel_checker({_, nil}, _Module, _ModuleMap) -> ok; spawn_parallel_checker(CheckerInfo, Module, ModuleMap) -> Log = diff --git a/lib/elixir/test/elixir/kernel/raise_test.exs b/lib/elixir/test/elixir/kernel/raise_test.exs index 47320f9bef..6dcdafbaaf 100644 --- a/lib/elixir/test/elixir/kernel/raise_test.exs +++ b/lib/elixir/test/elixir/kernel/raise_test.exs @@ -434,15 +434,15 @@ defmodule Kernel.RaiseTest do :code.purge(BadFunction.Missing) - result = - try do - fun.() - rescue - x in [BadFunctionError] -> Exception.message(x) - end - - assert result =~ - ~r/function #Function<[0-9]\.[0-9]*\/0[^>]*> is invalid, likely because it points to an old version of the code/ + try do + fun.() + rescue + x in [BadFunctionError] -> + assert Exception.message(x) =~ + ~r/function #Function<[0-9]\.[0-9]*\/0[^>]*> is invalid, likely because it points to an old version of the code/ + else + _ -> flunk("this should not be invoked") + end end test "badmatch error" do diff --git a/lib/elixir/test/elixir/module/types/infer_test.exs b/lib/elixir/test/elixir/module/types/infer_test.exs index 106ab0475a..656ee74748 100644 --- a/lib/elixir/test/elixir/module/types/infer_test.exs +++ b/lib/elixir/test/elixir/module/types/infer_test.exs @@ -7,14 +7,19 @@ defmodule Module.Types.InferTest do defmacro infer(config, do: block) do quote do - defmodule unquote(config).test do - unquote(block) - end - |> runtime_infer() + runtime_infer(unquote(config).test, unquote(Macro.escape(block))) end end - defp runtime_infer({:module, module, binary, _result}) do + defp runtime_infer(module, block) do + {{:module, _, binary, _}, []} = + Code.eval_quoted( + quote do + defmodule unquote(module), do: unquote(block) + end, + [] + ) + {: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, []) @@ -43,6 +48,16 @@ defmodule Module.Types.InferTest do assert types[{:fun4, 4}] == {:infer, [{args, atom([:ok])}]} end + test "infer with Elixir built-in", config do + types = + infer config do + def parse(string), do: Integer.parse(string) + end + + assert types[{:parse, 1}] == + {:infer, [{[dynamic()], dynamic(union(atom([:error]), tuple([integer(), term()])))}]} + end + test "merges patterns", config do types = infer config do