File 0617-dialyzer-Fix-bug-related-to-maps.patch of Package erlang

From c506aaf1066abce690cca44242c777f5e7cfc019 Mon Sep 17 00:00:00 2001
From: Hans Bolinder <hasse@erlang.org>
Date: Fri, 13 Aug 2021 10:51:10 +0200
Subject: [PATCH] dialyzer: Fix bug related to maps

The representation of some map types was not unique.
For example: #{0 => t(), pos_integer() => t()} and #{non_neg_integer() => t()}
were both possible.
---
 lib/dialyzer/src/erl_types.erl        |  74 +++++++++++++---
 lib/dialyzer/test/erl_types_SUITE.erl | 120 +++++++++++++++++++++++++-
 2 files changed, 178 insertions(+), 16 deletions(-)

diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl
index db3ca22559..50e408d382 100644
--- a/lib/hipe/cerl/erl_types.erl
+++ b/lib/hipe/cerl/erl_types.erl
@@ -96,7 +96,7 @@
 	 t_integer/0,
 	 t_integer/1,
 	 t_non_neg_integer/0,
-	 t_pos_integer/0,
+	 t_pos_integer/0, t_neg_integer/0,
 	 t_integers/1,
 	 t_iodata/0,
 	 t_iolist/0,
@@ -1713,12 +1713,12 @@ t_map(Pairs0, DefK0, DefV0) ->
       true  -> {?none, ?none};
       false -> {DefK1, DefV0}
     end,
-  {Pairs1, DefK, DefV}
+  {Pairs1, DefK3, DefV}
     = case is_singleton_type(DefK2) of
 	true  -> {mapdict_insert({DefK2, ?opt, DefV1}, Pairs0), ?none, ?none};
 	false -> {Pairs0,                                       DefK2, DefV1}
       end,
-  Pairs = normalise_map_optionals(Pairs1, DefK, DefV),
+  {Pairs, DefK} = normalise_map_optionals(Pairs1, DefK3, DefV),
   %% Validate invariants of the map representation.
   %% Since we needed to iterate over the arguments in order to normalise anyway,
   %% we might as well save us some future pain and do this even without
@@ -1732,20 +1732,66 @@ t_map(Pairs0, DefK0, DefV0) ->
     false -> ?map(Pairs, DefK, DefV)
   end.
 
-normalise_map_optionals([], _, _) -> [];
-normalise_map_optionals([E={K,?opt,?none}|T], DefK, DefV) ->
+normalise_map_optionals(Pairs, DefK, DefV) ->
+  case normalise_map_optionals(Pairs, DefK, DefV, [], defk_unchanged) of
+    {Pairs1, DefK1, defk_changed} ->
+      normalise_map_optionals(Pairs1, DefK1, DefV);
+    {Pairs1, DefK1, defk_unchanged} ->
+      {Pairs1, DefK1}
+  end.
+
+normalise_map_optionals([], DefK, _, Es, F) -> {lists:reverse(Es), DefK, F};
+normalise_map_optionals([E={K,?opt,?none}|T], DefK, DefV, Es, F) ->
   Diff = t_subtract(DefK, K),
   case t_is_subtype(K, DefK) andalso DefK =:= Diff of
-    true -> [E|normalise_map_optionals(T, DefK, DefV)];
-    false -> normalise_map_optionals(T, Diff, DefV)
+    true -> normalise_map_optionals(T, DefK, DefV, [E|Es], F);
+    false -> normalise_map_optionals(T, Diff, DefV, Es, F)
   end;
-normalise_map_optionals([E={K,?opt,V}|T], DefK, DefV) ->
-  case t_is_equal(V, DefV) andalso t_is_subtype(K, DefK) of
-    true -> normalise_map_optionals(T, DefK, DefV);
-    false -> [E|normalise_map_optionals(T, DefK, DefV)]
+normalise_map_optionals([E={K,?opt,V}|T], DefK, DefV, Es, F) ->
+  HowToHandleE =
+    case t_is_equal(V, DefV) of
+      true ->
+        case t_is_subtype(K, DefK) of
+          true -> skip;
+          false ->
+            case needs_to_be_merged(K, DefK) of
+              true -> add_to_default_key;
+              false -> keep
+            end
+        end;
+      false -> keep
+    end,
+  case HowToHandleE of
+    skip ->
+      normalise_map_optionals(T, DefK, DefV, Es, F);
+    keep ->
+      normalise_map_optionals(T, DefK, DefV, [E|Es], F);
+    add_to_default_key ->
+      normalise_map_optionals(T, t_sup(K, DefK), DefV, Es, defk_changed)
   end;
-normalise_map_optionals([E|T], DefK, DefV) ->
-  [E|normalise_map_optionals(T, DefK, DefV)].
+normalise_map_optionals([E|T], DefK, DefV, Es, F) ->
+  normalise_map_optionals(T, DefK, DefV, [E|Es], F).
+
+%% Return `true' if the first argument (a singleton) cannot be
+%% separated from the second argument (the default key) as that would
+%% represent equal map types by unequal terms. An example:
+%% `#{0 => t(), pos_integer() => t()}' is to be represented by
+%% `#{non_neg_integer() => t()}'.
+needs_to_be_merged(?int_set(Set), DefK) ->
+  [I] = set_to_list(Set),
+  Iplus = t_integer(I + 1),
+  Iminus = t_integer(I - 1),
+  InfPlus = t_inf(Iplus, DefK),
+  InfMinus = t_inf(Iminus, DefK),
+  not (t_is_none(InfPlus) andalso t_is_none(InfMinus));
+needs_to_be_merged(?atom(_Set), DefK) ->
+  InfAtom = t_inf(t_atom(), DefK),
+  not t_is_none(InfAtom);
+needs_to_be_merged(?nil, DefK) ->
+  InfNonEmpty = t_inf(t_nonempty_list(), DefK),
+  t_is_cons(InfNonEmpty);
+needs_to_be_merged(_, _) ->
+  false.
 
 validate_map_elements([{K1,_,_}|Rest=[{K2,_,_}|_]]) ->
   case is_singleton_type(K1) andalso K1 < K2 of
@@ -5584,7 +5630,7 @@ is_singleton_type(?nil) -> true;
 is_singleton_type(?atom(?any)) -> false;
 is_singleton_type(?atom(Set)) ->
   ordsets:size(Set) =:= 1;
-is_singleton_type(?int_range(V, V)) -> true;
+is_singleton_type(?int_range(V, V)) -> true; % cannot happen
 is_singleton_type(?int_set(Set)) ->
   ordsets:size(Set) =:= 1;
 is_singleton_type(_) ->
diff --git a/lib/hipe/test/erl_types_SUITE.erl b/lib/hipe/test/erl_types_SUITE.erl
index 7d7c144b69..bc735d3a60 100644
--- a/lib/hipe/test/erl_types_SUITE.erl
+++ b/lib/hipe/test/erl_types_SUITE.erl
@@ -15,7 +15,7 @@
 -module(erl_types_SUITE).
 
 -export([all/0,
-         consistency_and_to_string/1]).
+         consistency_and_to_string/1, map_multiple_representations/1]).
 
 %% Simplify calls into erl_types and avoid importing the entire module.
 -define(M, erl_types).
@@ -23,7 +23,7 @@
 -include_lib("common_test/include/ct.hrl").
 
 all() ->
-    [consistency_and_to_string].
+    [consistency_and_to_string, map_multiple_representations].
 
 consistency_and_to_string(_Config) ->
     %% Check consistency of types
@@ -195,3 +195,119 @@ consistency_and_to_string(_Config) ->
     "boolean()" = ?M:t_to_string(Union8),
     "{'false',_} | {'true',_}" = ?M:t_to_string(Union10),
     "{'true',integer()}" = ?M:t_to_string(?M:t_inf(Union10, ?M:t_tuple([?M:t_atom(true), ?M:t_integer()]))).
+
+%% OTP-17537.
+map_multiple_representations(_Config) ->
+    DefV = erl_types:t_atom(),
+    fun() ->
+            P2 = {erl_types:t_integer(0), optional, DefV},
+            Ps = [P2],
+            DefK = erl_types:t_pos_integer(),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{non_neg_integer()=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    fun() ->
+            P1 = {erl_types:t_integer(-1), optional, DefV},
+            P2 = {erl_types:t_integer(0), optional, DefV},
+            Ps = [P1, P2],
+            DefK = erl_types:t_pos_integer(),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{integer()=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    fun() ->
+            P1 = {erl_types:t_integer(0), optional, DefV}, % integer()
+            P2 = {erl_types:t_integer(1), optional, DefV}, % extra
+            Ps = [P1, P2],
+            DefK = erl_types:t_neg_integer(),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{integer()=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    fun() ->
+            P1 = {erl_types:t_nil(), optional, DefV},
+            Ps = [P1],
+            DefK = erl_types:t_nonempty_list(),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{[any()]=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    fun() ->
+            P1 = {erl_types:t_nil(), optional, DefV},
+            Ps = [P1],
+            DefK = erl_types:t_nonempty_string(),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{string()=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    fun() ->
+            P1 = {erl_types:t_nil(), optional, DefV},
+            Ps = [P1],
+            DefK = erl_types:t_sup(erl_types:t_nonempty_string(),
+                                   erl_types:t_nil()),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{string()=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    fun() ->
+            P1 = {erl_types:t_nil(), optional, DefV},
+            Ps = [P1],
+            DefK = erl_types:t_sup(erl_types:t_nonempty_string(),
+                                   erl_types:t_atom()),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{atom() | string()=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    fun() ->
+            P1 = {erl_types:t_integer(0), optional, DefV},
+            Ps = [P1],
+            DefK = erl_types:t_sup(erl_types:t_pos_integer(),
+                                   erl_types:t_atom()),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{atom() | non_neg_integer()=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    fun() ->
+            P1 = {erl_types:t_integer(8), optional, DefV},
+            Ps = [P1],
+            DefK = erl_types:t_from_range(9, 12),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{8 | 9 | 10 | 11 | 12=>atom()}" = erl_types:t_to_string(T)
+
+    end(),
+    fun() ->
+            P1 = {erl_types:t_integer(13), optional, DefV},
+            Ps = [P1],
+            DefK = erl_types:t_from_range(9, 12),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{9 | 10 | 11 | 12 | 13=>atom()}" = erl_types:t_to_string(T)
+
+    end(),
+    fun() ->
+            P1 = {erl_types:t_atom(a), optional, DefV},
+            Ps = [P1],
+            DefK = erl_types:t_sup([erl_types:t_atom(a01),
+                                    erl_types:t_atom(a02),
+                                    erl_types:t_atom(a03),
+                                    erl_types:t_atom(a04),
+                                    erl_types:t_atom(a05),
+                                    erl_types:t_atom(a06),
+                                    erl_types:t_atom(a07),
+                                    erl_types:t_atom(a08),
+                                    erl_types:t_atom(a09),
+                                    erl_types:t_atom(a10),
+                                    erl_types:t_atom(a11),
+                                    erl_types:t_atom(a12),
+                                    erl_types:t_atom(a13)]),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{atom()=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    fun() ->
+            P1 = {erl_types:t_atom(a), optional, DefV},
+            Ps = [P1],
+            DefK = erl_types:t_sup([erl_types:t_atom(b),
+                                    erl_types:t_atom(c)]),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{'a' | 'b' | 'c'=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    fun() ->
+            P1 = {erl_types:t_atom(a), optional, DefV},
+            Ps = [P1],
+            DefK = erl_types:t_atom(b),
+            T = erl_types:t_map(Ps, DefK, DefV),
+            "#{'a'=>atom(), 'b'=>atom()}" = erl_types:t_to_string(T)
+    end(),
+    ok.
-- 
2.31.1

openSUSE Build Service is sponsored by