How to explain strange semantics of map typespecs?

Hello!

Recently I have tried to figure out how typespecs for maps work, and found that area very puzzling.

I have created four sample cases and none of them worked either in a way I expected or in a consistent way.

Here they are:

  @spec hello() :: %{required(integer()) => integer(), required(binary()) => binary()}
  def hello() do
    %{1 => 1}
  end
  @spec hello() :: %{required(integer()) => integer(), required(binary()) => binary()}
  def hello() do
    %{"a" => "b"}
  end
  @spec hello() :: %{required(integer()) => integer(), required([]) => []}
  def hello() do
    %{[] => []}
  end
  @spec hello() :: %{required(integer()) => integer(), required([]) => []}
  def hello() do
    %{1 => 1}
  end

Since required assocs are required and their types do not overlap, I expect all four cases to fail dialyzer checks.

But! 1,2,3 succesfully pass dialyzer checks and only 4 fails.

How that could be explained?

3 Likes