File 2401-Fully-support-maps-in-ms_transform.patch of Package erlang

From cc043f8a7d52ad48b41bb9dd89e199d811d09164 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20G=C3=B6m=C3=B6ri?= <gomoripeti@gmail.com>
Date: Sat, 29 May 2021 12:55:58 +0200
Subject: [PATCH] Fully support maps in ms_transform

Before this change only map patterns worked and only in the shell via a
hack in `normalise/1` (It converted a map pattern AST to map, but it did
not convert a map expressions AST).

Now the transformation takes care of map patterns in MS head and map
expressions in MS guards/body and `normalise/1` only does what
`erl_parse:normalise/1`. Checking restrictions on map keys and values is
left to the PAM machine.
---
 lib/stdlib/src/ms_transform.erl        | 21 +++++---
 lib/stdlib/test/ms_transform_SUITE.erl | 70 ++++++++++++++++++++++++--
 lib/stdlib/test/qlc_SUITE.erl          |  6 +--
 3 files changed, 83 insertions(+), 14 deletions(-)

diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl
index f38b0eb905..dde8e572a3 100644
--- a/lib/stdlib/src/ms_transform.erl
+++ b/lib/stdlib/src/ms_transform.erl
@@ -756,7 +756,12 @@ tg({bin_element,Anno,X,Y,Z},B) ->
 
 tg({bin,Anno,List},B) ->
     {bin,Anno,[tg(X,B) || X <- List]};
-    
+
+tg({map_field_assoc, Anno, Field, Value}, B) ->
+    {map_field_assoc, Anno, tg(Field, B), tg(Value, B)};
+tg({map, Anno, List}, B) ->
+    {map, Anno, [tg(X, B) || X <- List]};
+
 tg(T,B) when is_tuple(T), tuple_size(T) >= 2 ->
     Element = element(1,T),
     Anno = element(2,T),
@@ -858,6 +863,9 @@ th({var,Anno,Name},B,OB) ->
 	Trans ->
 	    {{atom,Anno,Trans},B}
     end;
+th({map_field_exact,Anno,Field,Value},B,OB) ->
+    {[NField, NValue], NB} = th([Field, Value], B, OB),
+    {{map_field_assoc,Anno,NField,NValue}, NB};
 th([H|T],B,OB) ->
     {NH,NB} = th(H,B,OB),
     {NT,NNB} = th(T,NB,OB),
@@ -1134,12 +1142,11 @@ normalise({op,_,'++',A,B}) ->
     normalise(A) ++ normalise(B);
 normalise({tuple,_,Args}) ->
     list_to_tuple(normalise_list(Args));
-normalise({map,_,Pairs0}) ->
-    Pairs1 = lists:map(fun ({map_field_exact,_,K,V}) ->
-                               {normalise(K),normalise(V)}
-                       end,
-                       Pairs0),
-    maps:from_list(Pairs1);
+normalise({map,_,Pairs}) ->
+    maps:from_list(lists:map(fun
+		%% only allow '=>'
+		({map_field_assoc,_,K,V}) -> {normalise(K),normalise(V)}
+	    end, Pairs));
 %% Special case for unary +/-.
 normalise({op,_,'+',{char,_,I}}) -> I;
 normalise({op,_,'+',{integer,_,I}}) -> I;
diff --git a/lib/stdlib/test/ms_transform_SUITE.erl b/lib/stdlib/test/ms_transform_SUITE.erl
index c9dc7e5c1d..c34c7e9e69 100644
--- a/lib/stdlib/test/ms_transform_SUITE.erl
+++ b/lib/stdlib/test/ms_transform_SUITE.erl
@@ -30,8 +30,14 @@
 -export([from_shell/1]).
 -export([records/1]).
 -export([record_index/1]).
--export([multipass/1]).
+-export([map_pattern/1]).
+-export([map_expr_in_head/1]).
+-export([map_pattern_from_shell/1]).
+-export([map_expr_in_head_from_shell/1]).
+-export([map_exprs/1]).
+-export([map_exprs_from_shell/1]).
 -export([top_match/1]).
+-export([multipass/1]).
 -export([old_guards/1]).
 -export([autoimported/1]).
 -export([semicolon/1]).
@@ -63,7 +69,10 @@ all() ->
      record_index, multipass, bitsyntax, binary_bifs, record_defaults,
      andalso_orelse, float_1_function, action_function,
      warnings, no_warnings, top_match, old_guards, autoimported,
-     semicolon, eep37, otp_14454, otp_16824, unused_record].
+     semicolon, eep37, otp_14454, otp_16824, unused_record,
+     map_pattern, map_expr_in_head,
+     map_pattern_from_shell, map_expr_in_head_from_shell,
+     map_exprs, map_exprs_from_shell].
 
 groups() -> 
     [].
@@ -316,7 +325,7 @@ basic_ets(Config) when is_list(Config) ->
         compile_and_run(<<"ets:fun2ms(fun({\"foo\" ++ _, X}) -> X end)">>),
     ok.
 
-%% Tests basic ets:fun2ms.
+%% Tests basic dbg:fun2ms.
 basic_dbg(Config) when is_list(Config) ->
     setup(Config),
     [{[a,b],[],[{message,banan},{return_trace}]}] =
@@ -409,6 +418,59 @@ record_index(Config) when is_list(Config) ->
 		    <<"ets:fun2ms(fun({#a.a,A}) when A > #a.a -> #a.a end)">>),
     ok.
 
+map_pattern(Config) when is_list(Config) ->
+    setup(Config),
+    MS = [{{key, #{foo => '$1'}},[],['$1']}],
+    MS = compile_and_run(<<"ets:fun2ms(fun({key, #{foo := V}}) -> V end)">>),
+    ok.
+
+map_expr_in_head(Config) when is_list(Config) ->
+    setup(Config),
+    MS = [{{key, #{foo => '$1'}},[],['$1']}],
+    %% Accidentally it is possible to use => instead of := in the fun head,
+    %% in compiled code.
+    %% Although this is not an intended behaviour it is kept to
+    %% maintain backwards compatibility.
+    MS = compile_and_run(<<"ets:fun2ms(fun({key, #{foo => V}}) -> V end)">>),
+    ok.
+
+map_pattern_from_shell(Config) when is_list(Config) ->
+    MS = [{{key, #{foo => '$1'}},[],['$1']}],
+    MS = do_eval("ets:fun2ms(fun({key, #{foo := V}}) -> V end)"),
+    ok.
+
+map_expr_in_head_from_shell(Config) when is_list(Config) ->
+    setup(Config),
+    MS = [{{key, #{foo => '$1'}},[],['$1']}],
+    %% Accidentally it is possible to use => instead of := in the fun head,
+    %% in compiled code. This behaviour is kept for backwards compatibility.
+
+    %% As a side-effect, it is also possible to do the same with
+    %% `transform_from_shell/3', if the AST of the shell fun is
+    %% created bypassing the linter. (The linter would prevent
+    %% constructing such invalid syntax, so normally this is not
+    %% possible in the Erlang shell)
+    MS = do_eval("ets:fun2ms(fun({key, #{foo => V}}) -> V end)"),
+    ok.
+
+map_exprs(Config) when is_list(Config) ->
+    setup(Config),
+    MSGuard = [{{key,'$1','$2'}, [{'=:=','$1',#{foo => '$2'}}], ['$1']}],
+    MSGuard = compile_and_run(
+                <<"ets:fun2ms(fun({key, V1, V2}) when V1 =:= #{foo => V2} -> V1 end)">>),
+    MSBody = [{{key,'$1'}, [], [#{foo => '$1'}]}],
+    MSBody = compile_and_run(
+                <<"ets:fun2ms(fun({key, V}) -> #{foo => V} end)">>),
+    ok.
+
+map_exprs_from_shell(Config) when is_list(Config) ->
+    setup(Config),
+    MSGuard = [{{key,'$1','$2'}, [{'=:=','$1',#{foo => '$2'}}], ['$1']}],
+    MSGuard = do_eval("ets:fun2ms(fun({key, V1, V2}) when V1 =:= #{foo => V2} -> V1 end)"),
+    MSBody = [{{key,'$1'}, [], [#{foo => '$1'}]}],
+    MSBody = do_eval("ets:fun2ms(fun({key, V}) -> #{foo => V} end)"),
+    ok.
+
 %% Tests matching on top level in head to give alias for object().
 top_match(Config) when is_list(Config) ->
     setup(Config),
@@ -924,5 +986,5 @@ do_eval(String) ->
 			   [],
 			   String++".\n",1),
     {ok,Tree} = erl_parse:parse_exprs(T),
-    {value,Res,[]} =  erl_eval:exprs(Tree,[]),
+    {value,Res,[]} =  erl_eval:exprs(Tree,[],none,none),
     Res.
diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl
index 3bbb2a7e45..74c8aabf8e 100644
--- a/lib/stdlib/test/qlc_SUITE.erl
+++ b/lib/stdlib/test/qlc_SUITE.erl
@@ -2632,9 +2632,9 @@ info(Config) when is_list(Config) ->
              L = [{#{k => #{v => Fun}}, Fun}],
              H = qlc:q([Q || Q <- L, Q =:= {#{k => #{v => Fun}}, Fun}]),
              L = qlc:e(H),
-             {call,_,_,[{lc,_,{var,_,'Q'},
-                         [{generate,_,_,_},
-                          {op,_,_,_,_}]}]} =
+             {call,_,{remote,_,{atom,_,ets},{atom,_,match_spec_run}},
+                [_,
+                 {call,_,{remote,_,{atom,_,ets},{atom,_,match_spec_compile}},[_]}]} =
                 qlc:info(H, [{format,abstract_code}])">>
 
        ],
-- 
2.34.1

openSUSE Build Service is sponsored by