Existing issue
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]
Elixir 1.21.0-dev (a4ab118) (compiled with Erlang/OTP 28)
Operating system
any
Current behavior
numberize/1 rewrites negated BDD leaves so so == guard narrowing on difference types excludes values equal at runtime
Repro:
defmodule M do
def check(v, q) do
other =
case v do
{a} when is_float(a) -> {:float, a} # clause subtraction gives
other -> other # other : ... and not {float()}
end
w = if q, do: {1}, else: {1.5}
case w do
x when x == other -> {:matched, x}
_ -> :nomatch
end
end
end
M.check({1}, true) # => {:matched, {1}}
emits false positive warning:
warning: the following pattern will never match:
x == other
where "other" was given the type:
# type: dynamic(
({...} and not {float()}) or atom() or bitstring() or empty_list() or float() or fun() or
integer() or map() or non_empty_list(term(), term()) or pid() or port() or reference() or
{:float, float()}
)
# from: iex:3
other =
case v do
...
end
└─ iex:10: M.check/2
After numberization, not {float()} becomes not {number()}
numberize(:map, _) skips domain value types, so == on integer-keyed maps narrows to types excluding equal maps
Repro:
defmodule M do
def check do
y = %{1 => 1}
w = %{1 => 1.0}
case w do
x when x == y and map_size(x) == 1 -> {:eq, x}
_ -> :ne
end
end
end
M.check() # => {:eq, %{1 => 1.0}}
emits false positive:
warning: incompatible types assigned to "y":
%{integer() => integer()} !~ %{integer() => float()} and not empty_map()
where "y" was given the types:
# type: %{integer() => integer()}
# from: iex:16
y = %{1 => 1}
# type: %{integer() => float()} and not empty_map()
# from: iex:19
x == y
└─ iex:19: M.check/0
numberize should also map the domain value types inside the leaf tag
- comparison between distinct types ignores coercion inside containers
NOTE: this is acknowledged in code comments but it's relatively easy to produce code triggering that
Repro:
defmodule M3 do
def check(q) do
a = if q, do: {1}, else: {2}
b = {1.0}
case :ok do
:ok when a == b -> :eq
_ -> :ne
end
end
end
false positive:
warning: comparison between distinct types found:
a == b
given types:
dynamic({integer()}) == {float()}
where "a" was given the type:
# type: dynamic({integer()})
# from: iex:24
a =
if q do
{1}
else
{2}
end
where "b" was given the type:
# type: {float()}
# from: iex:25
b = {1.0}
While Elixir can compare across all types, you are comparing across types which are always disjoint, and the result is either always true or always false
└─ iex:27: M3.check/1
Expected behavior
Issues 1 and 2 should be addressed; 3 might be an intended tradeoff
Existing issue
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]
Elixir 1.21.0-dev (a4ab118) (compiled with Erlang/OTP 28)
Operating system
any
Current behavior
numberize/1rewrites negated BDD leaves so so==guard narrowing on difference types excludes values equal at runtimeRepro:
emits false positive warning:
After numberization,
not {float()}becomesnot {number()}numberize(:map, _)skips domain value types, so==on integer-keyed maps narrows to types excluding equal mapsRepro:
emits false positive:
numberize should also map the domain value types inside the leaf tag
NOTE: this is acknowledged in code comments but it's relatively easy to produce code triggering that
Repro:
false positive:
Expected behavior
Issues 1 and 2 should be addressed; 3 might be an intended tradeoff