Skip to content

Spec check regression due to __info__ spec being injected after explicit specs #59

@erszcz

Description

@erszcz

Consider the following module:

defmodule Test.TypedGenServer.Stage2.Server do
  @spec test :: any()
  def test do
    :ok
  end
end

This module, when type checked with the current master branch (60d7d3f) warns about:

iex(62)> recompile(); Gradient.type_check_file(:code.which( Test.TypedGenServer.Stage2.Server ), [:infer])
Compiling 1 file (.ex)
lib/typed_gen_server/stage2.ex: The spec test/0 on line 79 follows another spec, but only one spec per function clause is allowed

The expanded Erlang AST of the test module is:

iex(64)> Gradient.Debug.erlang_ast(Test.TypedGenServer.Stage2.Server)
{:ok,
 [
   {:attribute, 78, :file, {'lib/typed_gen_server/stage2.ex', 78}},
   {:attribute, 78, :module, Test.TypedGenServer.Stage2.Server},
   {:attribute, 78, :compile, [:no_auto_import]},
   {:attribute, 79, :spec,
    {{:test, 0},
     [{:type, 79, :fun, [{:type, 79, :product, []}, {:type, 79, :any, []}]}]}},
   {:attribute, 78, :export, [__info__: 1, test: 0]},
   {:attribute, 78, :spec,
    {{:__info__, 1},
     [
       {:type, 78, :fun,
        [
          {:type, 78, :product,
           [
             {:type, 78, :union,
              [
                {:atom, 78, :attributes},
                {:atom, 78, :compile},
                {:atom, 78, :functions},
                {:atom, 78, :macros},
                {:atom, 78, :md5},
                {:atom, 78, :exports_md5},
                {:atom, 78, :module},
                {:atom, 78, :deprecated}
              ]}
           ]},
          {:type, 78, :any, []}
        ]}
     ]}},
   {:function, 0, :__info__, 1,
    [
      {:clause, 0, [{:atom, 0, :module}], [],
       [{:atom, 0, Test.TypedGenServer.Stage2.Server}]},
      {:clause, 0, [{:atom, 0, :functions}], [],
       [
         {:cons, 0, {:tuple, 0, [{:atom, 0, :test}, {:integer, 0, 0}]},
          {nil, 0}}
       ]},
      {:clause, 0, [{:atom, 0, :macros}], [], [nil: 0]},
      {:clause, 0, [{:atom, 0, :exports_md5}], [],
       [
         {:bin, 0,
          [
            {:bin_element, 0,
             {:string, 0,
              [4, 28, 248, 174, 47, 116, 141, 76, 191, 12, 198, 77, 111, 213,
               73, 150]}, :default, :default}
          ]}
       ]},
      {:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :attributes}}], [],
       [
         {:call, 0,
          {:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
          [{:atom, 0, Test.TypedGenServer.Stage2.Server}, {:var, 0, :Key}]}
       ]},
      {:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :compile}}], [],
       [
         {:call, 0,
          {:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
          [{:atom, 0, Test.TypedGenServer.Stage2.Server}, {:var, 0, :Key}]}
       ]},
      {:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :md5}}], [],
       [
         {:call, 0,
          {:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
          [{:atom, 0, Test.TypedGenServer.Stage2.Server}, {:var, 0, :Key}]}
       ]},
      {:clause, 0, [{:atom, 0, :deprecated}], [], [nil: 0]}
    ]},
   {:function, 80, :test, 0, [{:clause, 80, [], [], [{:atom, 0, :ok}]}]}
 ]}

That is, there are two @spec attributes following after each other, which is not consistent with the new check. This probably stems from the fact that the Elixir compiler injects __info__ and its spec just after all other attributes and before any functions, i.e. exactly after the first explicit spec in the module (which is an attribute), but before the function the spec corresponds to.

We have to find a workaround for the spec check as this is going to be a common problem :|

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions