Skip to content

False positives on map literals with union/negation on variable keys #15909

Description

@lukaszsamson

Existing issue

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

Elixir and Erlang/OTP versions

Erlang/OTP 28 [erts-16.4.0.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]

Interactive Elixir (1.21.0-dev)

Operating system

any

Current behavior

Map literal expressions with variable keys produce too small types that do not contain observed runtime values and result in false positive warnings.

Issue 1: Map literal whose key is :a or integer() records :a as required

Repro:

    defmodule P22 do
      def fin_dom(c) do
        k = if c, do: :a, else: 5
        m = %{k => 1}
        case m do
          %{a: _} -> :has_a
          _ -> :no
        end
      end
    end

Result:

warning: the following clause cannot match because the previous clauses already matched all possible values:

    _ ->

it attempts to match on the result of:

    m

which has the already matched type:

    dynamic(%{integer() => integer(), a: integer()})

└─ iex:8: P22.fin_dom/1

Likely reason: closed_map/5 adds finite atoms as required keys even when domain_keys != []

Issue 2:

Map literals with a negated atom variable key mark the negated atom as not_set()

Repro:

    defmodule P21 do
      def neg_key(s1, s2) do
        k1 = String.to_atom(s1)
        k2 = case String.to_atom(s2) do :foo -> :not_foo; o -> o end
        m = %{k1 => :baz, k2 => :baz2}
        case m do
          %{foo: _} -> :has_foo
          _ -> :no
        end
      end

      def neg2(s1, s2) do
        k1 = case String.to_atom(s1) do :a -> :na; o -> o end
        k2 = case String.to_atom(s2) do :b -> :nb; o -> o end
        m = %{k1 => 1, k2 => 2}
        case m do
          %{b: _} -> :has_b
          _ -> :no
        end
      end
    end

Result:

warning: the following clause will never match:

    %{foo: _} ->

because it attempts to match on the result of:

    m

which has type:

    dynamic(%{atom() => :baz or :baz2, foo: not_set()})

└─ iex:9: P21.neg_key/2

warning: the following clause will never match:

    %{b: _} ->

because it attempts to match on the result of:

    m

which has type:

    dynamic(%{atom() => integer(), a: if_set(integer()), b: not_set()})

└─ iex:19: P21.neg2/2
IO.inspect({P21.neg_key("foo","bar"), P21.neg2("b","c")}) # {:has_foo, :has_b}

Note neg2 also shows the asymmetry: a is if_set (correct) but b is not_set (wrong)

Likely reason: closed_map/5 and union_negated/4 make every negated atom not_set() without consulting the :atom domain

Expected behavior

Proper types constructed, no warnings

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions