# We disable type inference across modules by setting
# infer_signatures to [] when compiling Elixir for
# deterministic reasons. Now we do one additional pass
# using the locally inferred types to infer all types
# for stdlib itself.
parent = self()
ebin = Path.expand("../ebin", __DIR__)

# Validate we are loading Elixir modules and that they are all in place
[:elixir] = Code.get_compiler_option(:infer_signatures)

[_ | _] =
  modules =
  for module <- Application.spec(:elixir, :modules),
      match?("Elixir." <> _, Atom.to_string(module)) do
    module
  end

# Do a quick sanity check that some modules are defined
true = URI in modules and Version.Requirement in modules

{time, modules_paths} =
  :timer.tc(fn ->
    {:ok, checker} = Module.ParallelChecker.start_link()

    try do
      modules
      |> Task.async_stream(
        fn module ->
          path = Path.join(ebin, "#{module}.beam")
          Module.ParallelChecker.put(parent, checker)
          cache = Module.ParallelChecker.get()
          binary = File.read!(path)

          {:ok, {_, [{:debug_info, debug_info}, {_, checker_blob}]}} =
            :beam_lib.chunks(binary, [:debug_info, ~c"ExCk"])

          {:debug_info_v1, _backend, {:elixir_v1, module_map, _specs}} = debug_info

          %{module: module, file: file, attributes: attributes, definitions: definitions} =
            module_map

          {_, checker} = :erlang.binary_to_term(checker_blob)
          env = :elixir_env.new()

          # We assume that all private functions have been invoked at this point
          private =
            for {fun_arity, kind, _, _} <- definitions, kind in [:defp, :defmacrop], do: fun_arity

          {signatures, _} =
            Module.Types.infer(module, file, attributes, definitions, private, env, cache)

          checker =
            update_in(checker.exports, fn exports ->
              for {fun, info} <- exports do
                {fun, %{info | sig: Map.get(signatures, fun, info.sig)}}
              end
            end)

          [{"ExCk", checker_chunk}] = :elixir_erl.checker_chunk(checker, [:deterministic])
          {:ok, ^module, chunks} = :beam_lib.all_chunks(binary)

          {:ok, new_binary} =
            chunks
            |> List.keyreplace(~c"ExCk", 0, {~c"ExCk", checker_chunk})
            |> :beam_lib.build_module()

          {module, path, new_binary}
        end,
        timeout: :infinity
      )
      # Get all results first to avoid writing files
      # while we are still doing inference
      |> Enum.to_list()
      |> Enum.map(fn {:ok, {module, path, new_binary}} ->
        File.write!(path, new_binary)
        {module, path}
      end)
    after
      Module.ParallelChecker.stop(checker)
    end
  end)

IO.puts(:stderr, ["Type inferred stdlib in ", Integer.to_string(div(time, 1000)), "ms"])

{time, _} =
  :timer.tc(fn ->
    # We start a new one so it uses the new cache
    {:ok, checker} = Module.ParallelChecker.start_link()
    Module.ParallelChecker.verify(checker, modules_paths)
  end)

IO.puts(:stderr, ["Type checked stdlib in ", Integer.to_string(div(time, 1000)), "ms"])
