Skip to content

nested :erlang.map_get/2 in guards causes false positive type warning #15717

Description

@madlep

Existing issue

  • I have searched existing issues and could not find a duplicate.

Elixir and Erlang/OTP versions

> elixir --version
Erlang/OTP 29 [erts-17.0.5] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit]

Elixir 1.20.3 (compiled with Erlang/OTP 29)

Operating system

macos

Current behavior

If pattern matching on a nested map, and nested calls to :erlang.map_get/2 are used to fetch nested values, the type checker is giving false warnings for code that should be valid.

Using same code outside of a guard seems to be OK.

Following code reproduces:

defmodule MapGetExampleTest do
  use ExUnit.Case

  test "single works as positive guard" do
    some_record = {:ok, %{"foo" => 123}}

    result =
      case some_record do
        {:ok, data} when :erlang.map_get("foo", data) == 123 -> :ok
        _ -> :error
      end

    assert result == :ok
  end

  test "single works as negative guard" do
    some_record = {:ok, %{"foo" => 123}}

    result =
      case some_record do
        # fine. no type warning
        {:ok, data} when :erlang.map_get("z", data) == 123 -> :ok
        _ -> :error
      end

    assert result == :error
  end

  test "non-guard positive gets work ok" do
    some_record = {:ok, %{"foo" => %{"bar" => 123}}}
    {:ok, data} = some_record
    # fine. no type warning
    assert :erlang.map_get("bar", :erlang.map_get("foo", data)) == 123
  end

  test "non-guard negative gets raise" do
    some_record = {:ok, %{"foo" => %{"bar" => 123}}}
    {:ok, data} = some_record
    # fine. no type warning
    assert_raise KeyError, fn -> :erlang.map_get("zz", :erlang.map_get("z", data)) == 123 end
  end

  test "nesting works as positive guard" do
    some_record = {:ok, %{"foo" => %{"bar" => 123}}}

    result =
      case some_record do
        # invalid, false-positive type warning
        {:ok, data} when :erlang.map_get("bar", :erlang.map_get("foo", data)) == 123 -> :ok
        _ -> :error
      end

    assert result == :ok
  end

  test "nesting works as negative guard" do
    some_record = {:ok, %{"foo" => %{"bar" => 123}}}

    result =
      case some_record do
        # invalid, false-positive type warning
        {:ok, data} when :erlang.map_get("zz", :erlang.map_get("z", data)) == 123 -> :ok
        _ -> :error
      end

    assert result == :error
  end
end
> mix test
Running ExUnit with seed: 869956, max_cases: 20

    warning: incompatible types given to :erlang.map_get/2:

        :erlang.map_get("bar", :erlang.map_get("foo", data))

    given types:

        binary(), term()

    but expected one of:

        term(), map()

    where "data" was given the type:

        # type: map()
        # from: test/map_get_example_test.exs:49:57
        :erlang.map_get("foo", data)

    type warning found at:
    │
 49 │         {:ok, data} when :erlang.map_get("bar", :erlang.map_get("foo", data)) == 123 -> :ok
    │                                  ~
    │
    └─ test/map_get_example_test.exs:49:34: MapGetExampleTest."test nesting works as positive guard"/1

    warning: incompatible types given to :erlang.map_get/2:

        :erlang.map_get("zz", :erlang.map_get("z", data))

    given types:

        binary(), term()

    but expected one of:

        term(), map()

    where "data" was given the type:

        # type: map()
        # from: test/map_get_example_test.exs:62:56
        :erlang.map_get("z", data)

    type warning found at:
    │
 62 │         {:ok, data} when :erlang.map_get("zz", :erlang.map_get("z", data)) == 123 -> :ok
    │                                  ~
    │
    └─ test/map_get_example_test.exs:62:34: MapGetExampleTest."test nesting works as negative guard"/1

......
Finished in 0.01 seconds (0.00s async, 0.01s sync)

Result: 6 passed

Could see a few map related open issues, but wasn't sure if this is part of the same overall issue.

Expected behavior

Test code above compiles and runs without type checker warnings.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions