diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index 958c9ea2695..d49e1b92669 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -861,30 +861,33 @@ defmodule Module.Types.Apply do skip_check? or not is_warning(stack) -> {result, context} - name in [:==, :"/="] and number_type?(left_type) and number_type?(right_type) -> + not disjoint?(left_type, right_type) -> {result, context} - # This check is incomplete. After all, we could have the number type nested - # inside a tuple or a list and the comparison would still be valid. - # However, nested comparison between distinct numbers is very uncommon, - # so we only check the direct value here. - disjoint?(left_type, right_type) -> - error = {:mismatched_comparison, left_type, right_type} - remote_error(error, :erlang, name, 2, expr, stack, context) + # `==` coerces integers and floats, at the top-level and nested inside + # containers alike, so `{1} == {1.0}` holds even though the types are + # disjoint as sets. `numberize/1` closes both sides over that coercion. + # `=:=` has no such coercion, so disjointedness settles it right away. + name in [:==, :"/="] and not disjoint?(numberize(left_type), numberize(right_type)) -> + {result, context} true -> - {result, context} + error = {:mismatched_comparison, left_type, right_type} + remote_error(error, :erlang, name, 2, expr, stack, context) end end defp mismatched_ordered_comparison(left_type, right_type, stack) do if is_warning(stack) do cond do - # These checks are incomplete. After all, we could have numbers and - # structs nested inside tuples or lists, but we only check the direct - # value here. - not (number_type?(left_type) and number_type?(right_type)) and - disjoint?(left_type, right_type) -> + # Ordered comparisons compare numbers by value, at the top-level and + # nested inside containers alike, so `numberize/1` closes both sides + # over that coercion before we call them distinct. + # + # The struct check below is still incomplete: we could have structs + # nested inside tuples or lists, but we only check the direct value. + disjoint?(left_type, right_type) and + disjoint?(numberize(left_type), numberize(right_type)) -> {:mismatched_comparison, left_type, right_type} match?({false, _}, map_fetch_key(dynamic(left_type), :__struct__)) and diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 0017204c4f1..7f7ab304dce 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -1036,15 +1036,6 @@ defmodule Module.Types.Descr do def bitstring_no_binary_type?(_), do: false - @doc """ - Optimized version of `not empty?(bare_intersection(integer() or float(), type))`. - """ - def number_type?(:term), do: true - def number_type?(%{dynamic: :term}), do: true - def number_type?(%{dynamic: %{bitmap: bitmap}}) when (bitmap &&& @bit_number) != 0, do: true - def number_type?(%{bitmap: bitmap}) when (bitmap &&& @bit_number) != 0, do: true - def number_type?(_), do: false - ## Bitmaps defp bitmap_to_quoted(val) do diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 3604340572e..b4c1f2bd446 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -1605,6 +1605,22 @@ defmodule Module.Types.ExprTest do assert typecheck!([x = 123, y = 456.0], min(x, y)) == dynamic(opt_union(integer(), float())) end + test "does not warn when numbers nested in containers coerce" do + # `{1} == {1.0}` is true at runtime, and `[1] < [2.0]` is not constant, + # even though the container types are disjoint as sets. + assert typecheck!([x = {123}, y = {456.0}], x == y) == boolean() + assert typecheck!([x = [123], y = [456.0]], x < y) == boolean() + assert typecheck!([x = %{a: 123}, y = %{a: 456.0}], x != y) == boolean() + + # `===` does not coerce, so the nested types stay distinct + assert typeerror!([x = {123}, y = {456.0}], x === y) =~ + "comparison between distinct types found" + + # Neither does a nested type that has no number to coerce with + assert typeerror!([x = {123}, y = {"foo"}], x == y) =~ + "comparison between distinct types found" + end + test "warns when comparison is constant" do assert typeerror!([x = :foo, y = 321], min(x, y)) == ~l"""