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