aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--erts/emulator/beam/external.c7
-rw-r--r--lib/compiler/src/beam_disasm.erl6
-rw-r--r--lib/compiler/src/sys_core_fold.erl26
-rw-r--r--lib/compiler/src/v3_core.erl3
-rw-r--r--lib/compiler/test/Makefile1
-rw-r--r--lib/compiler/test/compile_SUITE.erl96
-rw-r--r--lib/compiler/test/compile_SUITE_data/small.erl48
-rw-r--r--lib/compiler/test/compile_SUITE_data/small_maps.erl16
-rw-r--r--lib/compiler/test/map_SUITE.erl466
-rw-r--r--lib/compiler/test/warnings_SUITE.erl9
-rw-r--r--lib/ssh/src/ssh_file.erl26
-rw-r--r--lib/ssh/src/ssh_message.erl4
-rw-r--r--lib/syntax_tools/src/erl_prettypr.erl26
-rw-r--r--lib/syntax_tools/src/erl_syntax.erl257
-rw-r--r--lib/test_server/src/ts.erl92
15 files changed, 978 insertions, 105 deletions
diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c
index a4cc3435c3..9fb2dbd8bf 100644
--- a/erts/emulator/beam/external.c
+++ b/erts/emulator/beam/external.c
@@ -1169,6 +1169,7 @@ typedef struct {
Eterm* hp_end;
int remaining_n;
char* remaining_bytes;
+ Eterm* maps_head;
} B2TDecodeContext;
typedef struct {
@@ -1486,6 +1487,7 @@ static Eterm binary_to_term_int(Process* p, Uint32 flags, Eterm bin, Binary* con
ctx->u.dc.hp_start = HAlloc(p, ctx->heap_size);
ctx->u.dc.hp = ctx->u.dc.hp_start;
ctx->u.dc.hp_end = ctx->u.dc.hp_start + ctx->heap_size;
+ ctx->u.dc.maps_head = NULL;
ctx->state = B2TDecode;
/*fall through*/
case B2TDecode:
@@ -2878,7 +2880,7 @@ dec_term(ErtsDistExternal *edep, Eterm** hpp, byte* ep, ErlOffHeap* off_heap,
int n;
ErtsAtomEncoding char_enc;
register Eterm* hp; /* Please don't take the address of hp */
- Eterm *maps_head = NULL; /* for validation of maps */
+ Eterm *maps_head; /* for validation of maps */
Eterm* next;
SWord reds;
@@ -2888,6 +2890,7 @@ dec_term(ErtsDistExternal *edep, Eterm** hpp, byte* ep, ErlOffHeap* off_heap,
next = ctx->u.dc.next;
ep = ctx->u.dc.ep;
hpp = &ctx->u.dc.hp;
+ maps_head = ctx->u.dc.maps_head;
if (ctx->state != B2TDecode) {
int n_limit = reds;
@@ -2968,6 +2971,7 @@ dec_term(ErtsDistExternal *edep, Eterm** hpp, byte* ep, ErlOffHeap* off_heap,
reds = ERTS_SWORD_MAX;
next = objp;
*next = (Eterm) (UWord) NULL;
+ maps_head = NULL;
}
hp = *hpp;
@@ -3780,6 +3784,7 @@ dec_term_atom_common:
ctx->u.dc.ep = ep;
ctx->u.dc.next = next;
ctx->u.dc.hp = hp;
+ ctx->u.dc.maps_head = maps_head;
ctx->reds = 0;
return NULL;
}
diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl
index e0d0d0fd1d..57fdf95677 100644
--- a/lib/compiler/src/beam_disasm.erl
+++ b/lib/compiler/src/beam_disasm.erl
@@ -1134,7 +1134,7 @@ resolve_inst({line,[Index]},_,_,_) ->
{line,resolve_arg(Index)};
%%
-%% R17A.
+%% 17.0
%%
resolve_inst({put_map_assoc,Args},_,_,_) ->
[FLbl,Src,Dst,{u,N},{{z,1},{u,_Len},List0}] = Args,
@@ -1150,6 +1150,10 @@ resolve_inst({is_map,Args0},_,_,_) ->
[FLbl|Args] = resolve_args(Args0),
{test, is_map, FLbl, Args};
+resolve_inst({has_map_field,Args0},_,_,_) ->
+ [FLbl|Args] = resolve_args(Args0),
+ {test,has_map_field,FLbl,Args};
+
resolve_inst({get_map_element,Args},_,_,_) ->
[FLbl,Src,Key,Dst] = resolve_args(Args),
{get_map_element,FLbl,Src,Key,Dst};
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index b7bbf40b4a..e302e2324d 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -305,6 +305,10 @@ expr(#c_let{}=Let, Ctxt, Sub) ->
%% Now recursively re-process the new expression.
expr(Expr, Ctxt, sub_new_preserve_types(Sub))
end;
+expr(#c_letrec{body=#c_var{}}=Letrec, effect, _Sub) ->
+ %% This is named fun in an 'effect' context. Warn and ignore.
+ add_warning(Letrec, useless_building),
+ void();
expr(#c_letrec{defs=Fs0,body=B0}=Letrec, Ctxt, Sub) ->
Fs1 = map(fun ({Name,Fb}) ->
{Name,expr(Fb, {letrec,Ctxt}, Sub)}
@@ -598,6 +602,14 @@ eval_binary_1([#c_bitstr{val=#c_literal{val=Val},size=#c_literal{val=Sz},
error:_ ->
throw(impossible)
end;
+eval_binary_1([#c_bitstr{val=#c_literal{},size=#c_literal{},
+ unit=#c_literal{},type=#c_literal{},
+ flags=#c_cons{}=Flags}=Bitstr|Ss], Acc0) ->
+ case cerl:fold_literal(Flags) of
+ #c_literal{} = Flags1 ->
+ eval_binary_1([Bitstr#c_bitstr{flags=Flags1}|Ss], Acc0);
+ _ -> throw(impossible)
+ end;
eval_binary_1([], Acc) -> Acc;
eval_binary_1(_, _) -> throw(impossible).
@@ -1536,9 +1548,17 @@ map_pair_pattern_list(Ps0, Isub, Osub0) ->
{Ps,{_,Osub}} = mapfoldl(fun map_pair_pattern/2, {Isub,Osub0}, Ps0),
{Ps,Osub}.
-map_pair_pattern(#c_map_pair{op=#c_literal{val=exact},key=K0,val=V0}=Pair, {Isub,Osub0}) ->
- {K,Osub1} = pattern(K0, Isub, Osub0),
- {V,Osub} = pattern(V0, Isub, Osub1),
+map_pair_pattern(#c_map_pair{op=#c_literal{val=exact},key=K0,val=V0}=Pair,{Isub,Osub0}) ->
+ {K,Osub1} = case cerl:type(K0) of
+ binary ->
+ K1 = eval_binary(K0),
+ case cerl:type(K1) of
+ literal -> {K1,Osub0};
+ _ -> pattern(K0,Isub,Osub0)
+ end;
+ _ -> pattern(K0,Isub,Osub0)
+ end,
+ {V,Osub} = pattern(V0,Isub,Osub1),
{Pair#c_map_pair{key=K,val=V},{Isub,Osub}}.
bin_pattern_list(Ps0, Isub, Osub0) ->
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index e30bfa729c..59863f52ec 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -2088,7 +2088,8 @@ cexpr(#ifun{anno=#a{us=Us0}=A0,name={named,Name},fc=#iclause{pats=Ps}}=Fun0,
RecVar = #c_var{name={Name,length(Ps)}},
Let = #c_let{vars=[#c_var{name=Name}],arg=RecVar,body=Body},
CFun1 = CFun0#c_fun{body=Let},
- Letrec = #c_letrec{defs=[{RecVar,CFun1}],
+ Letrec = #c_letrec{anno=A0#a.anno,
+ defs=[{RecVar,CFun1}],
body=RecVar},
{Letrec,[],Us1,St1}
end;
diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile
index 51b3064589..0a637a07cd 100644
--- a/lib/compiler/test/Makefile
+++ b/lib/compiler/test/Makefile
@@ -26,6 +26,7 @@ MODULES= \
guard_SUITE \
inline_SUITE \
lc_SUITE \
+ map_SUITE \
match_SUITE \
misc_SUITE \
num_bif_SUITE \
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index de35ebc7bd..34c4b1e264 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -286,57 +286,67 @@ cond_and_ifdef(Config) when is_list(Config) ->
ok.
listings(Config) when is_list(Config) ->
- ?line Dog = test_server:timetrap(test_server:minutes(8)),
- ?line DataDir = ?config(data_dir, Config),
- ?line PrivDir = ?config(priv_dir, Config),
- ?line Simple = filename:join(DataDir, simple),
- ?line TargetDir = filename:join(PrivDir, listings),
- ?line ok = file:make_dir(TargetDir),
+ Dog = test_server:timetrap(test_server:minutes(8)),
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ ok = do_file_listings(DataDir, PrivDir, [
+ "simple",
+ "small",
+ "small_maps"
+ ]),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+do_file_listings(_, _, []) -> ok;
+do_file_listings(DataDir, PrivDir, [File|Files]) ->
+ Simple = filename:join(DataDir, File),
+ TargetDir = filename:join(PrivDir, listings),
+ ok = file:make_dir(TargetDir),
%% Test all dedicated listing options.
- ?line do_listing(Simple, TargetDir, 'S'),
- ?line do_listing(Simple, TargetDir, 'E'),
- ?line do_listing(Simple, TargetDir, 'P'),
- ?line do_listing(Simple, TargetDir, dpp, ".pp"),
- ?line do_listing(Simple, TargetDir, dabstr, ".abstr"),
- ?line do_listing(Simple, TargetDir, dexp, ".expand"),
- ?line do_listing(Simple, TargetDir, dcore, ".core"),
- ?line do_listing(Simple, TargetDir, doldinline, ".oldinline"),
- ?line do_listing(Simple, TargetDir, dinline, ".inline"),
- ?line do_listing(Simple, TargetDir, dcore, ".core"),
- ?line do_listing(Simple, TargetDir, dcopt, ".copt"),
- ?line do_listing(Simple, TargetDir, dsetel, ".dsetel"),
- ?line do_listing(Simple, TargetDir, dkern, ".kernel"),
- ?line do_listing(Simple, TargetDir, dlife, ".life"),
- ?line do_listing(Simple, TargetDir, dcg, ".codegen"),
- ?line do_listing(Simple, TargetDir, dblk, ".block"),
- ?line do_listing(Simple, TargetDir, dbool, ".bool"),
- ?line do_listing(Simple, TargetDir, dtype, ".type"),
- ?line do_listing(Simple, TargetDir, ddead, ".dead"),
- ?line do_listing(Simple, TargetDir, djmp, ".jump"),
- ?line do_listing(Simple, TargetDir, dclean, ".clean"),
- ?line do_listing(Simple, TargetDir, dpeep, ".peep"),
- ?line do_listing(Simple, TargetDir, dopt, ".optimize"),
+ do_listing(Simple, TargetDir, 'S'),
+ do_listing(Simple, TargetDir, 'E'),
+ do_listing(Simple, TargetDir, 'P'),
+ do_listing(Simple, TargetDir, dpp, ".pp"),
+ do_listing(Simple, TargetDir, dabstr, ".abstr"),
+ do_listing(Simple, TargetDir, dexp, ".expand"),
+ do_listing(Simple, TargetDir, dcore, ".core"),
+ do_listing(Simple, TargetDir, doldinline, ".oldinline"),
+ do_listing(Simple, TargetDir, dinline, ".inline"),
+ do_listing(Simple, TargetDir, dcore, ".core"),
+ do_listing(Simple, TargetDir, dcopt, ".copt"),
+ do_listing(Simple, TargetDir, dsetel, ".dsetel"),
+ do_listing(Simple, TargetDir, dkern, ".kernel"),
+ do_listing(Simple, TargetDir, dlife, ".life"),
+ do_listing(Simple, TargetDir, dcg, ".codegen"),
+ do_listing(Simple, TargetDir, dblk, ".block"),
+ do_listing(Simple, TargetDir, dbool, ".bool"),
+ do_listing(Simple, TargetDir, dtype, ".type"),
+ do_listing(Simple, TargetDir, ddead, ".dead"),
+ do_listing(Simple, TargetDir, djmp, ".jump"),
+ do_listing(Simple, TargetDir, dclean, ".clean"),
+ do_listing(Simple, TargetDir, dpeep, ".peep"),
+ do_listing(Simple, TargetDir, dopt, ".optimize"),
%% First clean up.
- ?line Listings = filename:join(PrivDir, listings),
- ?line lists:foreach(fun(F) -> ok = file:delete(F) end,
- filelib:wildcard(filename:join(Listings, "*"))),
+ Listings = filename:join(PrivDir, listings),
+ lists:foreach(fun(F) -> ok = file:delete(F) end,
+ filelib:wildcard(filename:join(Listings, "*"))),
%% Test options that produce a listing file if 'binary' is not given.
- ?line do_listing(Simple, TargetDir, to_pp, ".P"),
- ?line do_listing(Simple, TargetDir, to_exp, ".E"),
- ?line do_listing(Simple, TargetDir, to_core0, ".core"),
- ?line ok = file:delete(filename:join(Listings, "simple.core")),
- ?line do_listing(Simple, TargetDir, to_core, ".core"),
- ?line do_listing(Simple, TargetDir, to_kernel, ".kernel"),
+ do_listing(Simple, TargetDir, to_pp, ".P"),
+ do_listing(Simple, TargetDir, to_exp, ".E"),
+ do_listing(Simple, TargetDir, to_core0, ".core"),
+ ok = file:delete(filename:join(Listings, File ++ ".core")),
+ do_listing(Simple, TargetDir, to_core, ".core"),
+ do_listing(Simple, TargetDir, to_kernel, ".kernel"),
%% Final clean up.
- ?line lists:foreach(fun(F) -> ok = file:delete(F) end,
- filelib:wildcard(filename:join(Listings, "*"))),
- ?line ok = file:del_dir(Listings),
- ?line test_server:timetrap_cancel(Dog),
- ok.
+ lists:foreach(fun(F) -> ok = file:delete(F) end,
+ filelib:wildcard(filename:join(Listings, "*"))),
+ ok = file:del_dir(Listings),
+
+ do_file_listings(DataDir,PrivDir,Files).
listings_big(Config) when is_list(Config) ->
?line Dog = test_server:timetrap(test_server:minutes(10)),
diff --git a/lib/compiler/test/compile_SUITE_data/small.erl b/lib/compiler/test/compile_SUITE_data/small.erl
new file mode 100644
index 0000000000..37cd270e50
--- /dev/null
+++ b/lib/compiler/test/compile_SUITE_data/small.erl
@@ -0,0 +1,48 @@
+-module(small).
+
+-export([go/0,go/2]).
+
+
+-small_attribute({value,3}).
+
+go() -> go(3, 3.0).
+go(A,B) ->
+ V1 = A + B,
+ V2 = A * B,
+ V3 = V1 / V2,
+ V4 = V3 / 0.3,
+ V5 = V1 + V2 + V3 + V4,
+ try
+ R = call(<<"wazzup">>, A),
+ {A,B,V5,R,t(),recv()}
+ catch
+ C:E ->
+ {error, C, E}
+ end.
+
+-spec call(binary(), term()) -> binary().
+
+call(<<"wa", B/binary>>,V) when is_integer(V) -> B;
+call(B,_) -> B.
+
+t() ->
+ <<23:32, V:14, _:2, B/binary>> = id(<<"wazzup world">>),
+ {V,B}.
+
+recv() ->
+ F = fun() ->
+ receive
+ 1 -> ok;
+ 2 -> ok;
+ 3 -> ok;
+ a -> ok;
+ _ -> none
+ after 0 -> tmo
+ end
+ end,
+ tmo = F(),
+ ok.
+
+
+id(I) -> I.
+
diff --git a/lib/compiler/test/compile_SUITE_data/small_maps.erl b/lib/compiler/test/compile_SUITE_data/small_maps.erl
new file mode 100644
index 0000000000..a17a136a7d
--- /dev/null
+++ b/lib/compiler/test/compile_SUITE_data/small_maps.erl
@@ -0,0 +1,16 @@
+-module(small_maps).
+
+-export([go/0,go/1]).
+
+go() ->
+ go(1337).
+
+go(V0) ->
+ M0 = #{ a => 1, val => V0},
+ V1 = get_val(M0),
+ M1 = M0#{ val := [V0,V1] },
+ {some_val,[1337,{some_val,1337}]} = get_val(M1),
+ ok.
+
+get_val(#{ "wazzup" := _, val := V}) -> V;
+get_val(#{ val := V }) -> {some_val, V}.
diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl
new file mode 100644
index 0000000000..0ba5d5dd0d
--- /dev/null
+++ b/lib/compiler/test/map_SUITE.erl
@@ -0,0 +1,466 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(map_SUITE).
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2
+ ]).
+
+-export([
+ t_build_and_match_literals/1,
+ t_update_literals/1,t_match_and_update_literals/1,
+ t_update_map_expressions/1,
+ t_update_assoc/1,t_update_exact/1,
+ t_guard_bifs/1, t_guard_sequence/1, t_guard_update/1,
+ t_guard_receive/1, t_guard_fun/1,
+ t_list_comprehension/1,
+ t_map_sort_literals/1,
+ t_map_size/1,
+
+ %% warnings
+ t_warn_useless_build/1,
+ t_warn_pair_key_overloaded/1,
+
+ %% not covered in 17.0-rc1
+ t_build_and_match_over_alloc/1,
+ t_build_and_match_empty_val/1,
+ t_build_and_match_val/1
+
+ %% errors in 17.0-rc1
+
+ ]).
+
+suite() -> [].
+
+all() -> [
+ t_build_and_match_literals,
+ t_update_literals, t_match_and_update_literals,
+ t_update_map_expressions,
+ t_update_assoc,t_update_exact,
+ t_guard_bifs, t_guard_sequence, t_guard_update,
+ t_guard_receive,t_guard_fun, t_list_comprehension,
+ t_map_sort_literals,
+
+ %% warnings
+ t_warn_useless_build,
+ t_warn_pair_key_overloaded,
+
+ %% not covered in 17.0-rc1
+ t_build_and_match_over_alloc,
+ t_build_and_match_empty_val,
+ t_build_and_match_val
+
+ %% errors in 17.0-rc1
+
+ ].
+
+groups() -> [].
+
+init_per_suite(Config) -> Config.
+end_per_suite(_Config) -> ok.
+
+init_per_group(_GroupName, Config) -> Config.
+end_per_group(_GroupName, Config) -> Config.
+
+%% tests
+
+t_build_and_match_literals(Config) when is_list(Config) ->
+ #{} = id(#{}),
+ #{1:=a} = id(#{1=>a}),
+ #{1:=a,2:=b} = id(#{1=>a,2=>b}),
+ #{1:=a,2:=b,3:="c"} = id(#{1=>a,2=>b,3=>"c"}),
+ #{1:=a,2:=b,3:="c","4":="d"} = id(#{1=>a,2=>b,3=>"c","4"=>"d"}),
+ #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>} =
+ id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>}),
+ #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>,{"6",7}:="f"} =
+ id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f"}),
+ #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>,{"6",7}:="f",8:=g} =
+ id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f",8=>g}),
+
+ #{<<"hi all">> := 1} = id(#{<<"hi",32,"all">> => 1}),
+
+ #{a:=X,a:=X=3,b:=4} = id(#{a=>3,b=>4}), % weird but ok =)
+
+ #{ a:=#{ b:=#{c := third, b:=second}}, b:=first} =
+ id(#{ b=>first, a=>#{ b=>#{c => third, b=> second}}}),
+
+ M = #{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first},
+ M = #{ map_1:=#{ map_2:=#{value_3 := third}, value_2:= second}, value_1:=first} =
+ id(#{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first}),
+
+ %% error case
+ %V = 32,
+ %{'EXIT',{{badmatch,_},_}} = (catch (#{<<"hi all">> => 1} = id(#{<<"hi",V,"all">> => 1}))),
+ {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3,x:=2} = id(#{x=>3}))),
+ {'EXIT',{{badmatch,_},_}} = (catch (#{x:=2} = id(#{x=>3}))),
+ {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = id({a,b,c}))),
+ {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = id(#{y=>3}))),
+ {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = id(#{x=>"three"}))),
+ ok.
+
+
+t_map_size(Config) when is_list(Config) ->
+ 0 = map_size(id(#{})),
+ 1 = map_size(id(#{a=>1})),
+ 1 = map_size(id(#{a=>"wat"})),
+ 2 = map_size(id(#{a=>1, b=>2})),
+ 3 = map_size(id(#{a=>1, b=>2, b=>"3","33"=><<"n">>})),
+
+ true = map_is_size(#{a=>1}, 1),
+ true = map_is_size(#{a=>1, a=>2}, 1),
+ M = #{ "a" => 1, "b" => 2},
+ true = map_is_size(M, 2),
+ false = map_is_size(M, 3),
+ true = map_is_size(M#{ "a" => 2}, 2),
+ false = map_is_size(M#{ "c" => 2}, 2),
+
+ %% Error cases.
+ {'EXIT',{badarg,_}} = (catch map_size([])),
+ {'EXIT',{badarg,_}} = (catch map_size(<<1,2,3>>)),
+ {'EXIT',{badarg,_}} = (catch map_size(1)),
+ ok.
+
+map_is_size(M,N) when map_size(M) =:= N -> true;
+map_is_size(_,_) -> false.
+
+% test map updates without matching
+t_update_literals(Config) when is_list(Config) ->
+ Map = #{x=>1,y=>2,z=>3,q=>4},
+ #{x:="d",q:="4"} = loop_update_literals_x_q(Map, [
+ {"a","1"},{"b","2"},{"c","3"},{"d","4"}
+ ]),
+ ok.
+
+loop_update_literals_x_q(Map, []) -> Map;
+loop_update_literals_x_q(Map, [{X,Q}|Vs]) ->
+ loop_update_literals_x_q(Map#{q=>Q,x=>X},Vs).
+
+% test map updates with matching
+t_match_and_update_literals(Config) when is_list(Config) ->
+ Map = #{x=>0,y=>"untouched",z=>"also untouched",q=>1},
+ #{x:=16,q:=21,y:="untouched",z:="also untouched"} = loop_match_and_update_literals_x_q(Map, [
+ {1,2},{3,4},{5,6},{7,8}
+ ]),
+ M0 = id(#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
+ 4 => number, 18446744073709551629 => wat}),
+ M1 = id(#{}),
+ M2 = M1#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
+ 4 => number, 18446744073709551629 => wat},
+ M0 = M2,
+
+ #{ 4 := another_number, int := 3 } = M2#{ 4 => another_number },
+ ok.
+
+loop_match_and_update_literals_x_q(Map, []) -> Map;
+loop_match_and_update_literals_x_q(#{q:=Q0,x:=X0} = Map, [{X,Q}|Vs]) ->
+ loop_match_and_update_literals_x_q(Map#{q=>Q0+Q,x=>X0+X},Vs).
+
+
+t_update_map_expressions(Config) when is_list(Config) ->
+ M = maps:new(),
+ #{ a := 1 } = M#{a => 1},
+
+ #{ b := 2 } = (maps:new())#{ b => 2 },
+
+ #{ a :=42, b:=42, c:=42 } = (maps:from_list([{a,1},{b,2},{c,3}]))#{ a := 42, b := 42, c := 42 },
+ #{ "a" :=1, "b":=42, "c":=42 } = (maps:from_list([{"a",1},{"b",2}]))#{ "b" := 42, "c" => 42 },
+
+ %% Error cases, FIXME: should be 'badmap'?
+ {'EXIT',{badarg,_}} = (catch (id(<<>>))#{ a := 42, b => 2 }),
+ {'EXIT',{badarg,_}} = (catch (id([]))#{ a := 42, b => 2 }),
+ ok.
+
+
+t_update_assoc(Config) when is_list(Config) ->
+ M0 = id(#{1=>a,2=>b,3.0=>c,4=>d,5=>e}),
+
+ M1 = M0#{1=>42,2=>100,4=>[a,b,c]},
+ #{1:=42,2:=100,3.0:=c,4:=[a,b,c],5:=e} = M1,
+ #{1:=42,2:=b,4:=d,5:=e,2.0:=100,3.0:=c,4.0:=[a,b,c]} = M0#{1.0=>float,1:=42,2.0=>wrong,2.0=>100,4.0=>[a,b,c]},
+
+ M2 = M0#{3.0=>new},
+ #{1:=a,2:=b,3.0:=new,4:=d,5:=e} = M2,
+ M2 = M0#{3.0:=wrong,3.0=>new},
+
+ %% Errors cases.
+ BadMap = id(badmap),
+ {'EXIT',{badarg,_}} = (catch BadMap#{nonexisting=>val}),
+
+ ok.
+
+t_update_exact(Config) when is_list(Config) ->
+ M0 = id(#{1=>a,2=>b,3.0=>c,4=>d,5=>e}),
+
+ M1 = M0#{1:=42,2:=100,4:=[a,b,c]},
+ #{1:=42,2:=100,3.0:=c,4:=[a,b,c],5:=e} = M1,
+ M1 = M0#{1:=wrong,1=>42,2=>wrong,2:=100,4:=[a,b,c]},
+
+ M2 = M0#{3.0:=new},
+ #{1:=a,2:=b,3.0:=new,4:=d,5:=e} = M2,
+ M2 = M0#{3.0=>wrong,3.0:=new},
+ M2 = M0#{3=>wrong,3.0:=new},
+
+ %% Errors cases.
+ {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}),
+ {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}),
+ {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}),
+ {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}),
+
+ ok.
+
+t_guard_bifs(Config) when is_list(Config) ->
+ true = map_guard_head(#{a=>1}),
+ false = map_guard_head([]),
+ true = map_guard_body(#{a=>1}),
+ false = map_guard_body({}),
+ true = map_guard_pattern(#{a=>1, <<"hi">> => "hi" }),
+ false = map_guard_pattern("list"),
+ ok.
+
+map_guard_head(M) when is_map(M) -> true;
+map_guard_head(_) -> false.
+
+map_guard_body(M) -> is_map(M).
+
+map_guard_pattern(#{}) -> true;
+map_guard_pattern(_) -> false.
+
+t_guard_sequence(Config) when is_list(Config) ->
+ {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>id("a")}),
+ {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>id("b")}),
+ {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>id("c")}),
+ {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>id("d")}),
+ {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>id("e")}),
+
+ {1,M1} = map_guard_sequence_2(M1 = id(#{a=>3})),
+ {2,M2} = map_guard_sequence_2(M2 = id(#{a=>4, b=>4})),
+ {3,gg,M3} = map_guard_sequence_2(M3 = id(#{a=>gg, b=>4})),
+ {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(#{a=>sc, b=>3, c=>sc2})),
+ {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(#{a=>kk, b=>other, c=>sc2})),
+
+ %% error case
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>id("e")})),
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})),
+ ok.
+
+map_guard_sequence_1(#{seq:=1=Seq, val:=Val}) -> {Seq,Val};
+map_guard_sequence_1(#{seq:=2=Seq, val:=Val}) -> {Seq,Val};
+map_guard_sequence_1(#{seq:=3=Seq, val:=Val}) -> {Seq,Val};
+map_guard_sequence_1(#{seq:=4=Seq, val:=Val}) -> {Seq,Val};
+map_guard_sequence_1(#{seq:=5=Seq, val:=Val}) -> {Seq,Val}.
+
+map_guard_sequence_2(#{ a:=3 }=M) -> {1, M};
+map_guard_sequence_2(#{ a:=4 }=M) -> {2, M};
+map_guard_sequence_2(#{ a:=X, a:=X, b:=4 }=M) -> {3,X,M};
+map_guard_sequence_2(#{ a:=X, a:=Y, b:=3 }=M) when X =:= Y -> {4,X,Y,M};
+map_guard_sequence_2(#{ a:=X, a:=Y }=M) when X =:= Y -> {5,X,Y,M}.
+
+
+t_guard_update(Config) when is_list(Config) ->
+ error = map_guard_update(#{},#{}),
+ first = map_guard_update(#{}, #{x=>first}),
+ second = map_guard_update(#{y=>old}, #{x=>second,y=>old}),
+ third = map_guard_update(#{x=>old,y=>old}, #{x=>third,y=>old}),
+ ok.
+
+map_guard_update(M1, M2) when M1#{x=>first} =:= M2 -> first;
+map_guard_update(M1, M2) when M1#{x=>second} =:= M2 -> second;
+map_guard_update(M1, M2) when M1#{x:=third} =:= M2 -> third;
+map_guard_update(_, _) -> error.
+
+t_guard_receive(Config) when is_list(Config) ->
+ M0 = #{ id => 0 },
+ Pid = spawn_link(fun() -> guard_receive_loop() end),
+ Big = 36893488147419103229,
+ B1 = <<"some text">>,
+ B2 = <<"was appended">>,
+ B3 = <<B1/binary, B2/binary>>,
+
+ #{id:=1, res:=Big} = M1 = call(Pid, M0#{op=>sub,in=>{1 bsl 65, 3}}),
+ #{id:=2, res:=26} = M2 = call(Pid, M1#{op=>idiv,in=>{53,2}}),
+ #{id:=3, res:=832} = M3 = call(Pid, M2#{op=>imul,in=>{26,32}}),
+ #{id:=4, res:=4} = M4 = call(Pid, M3#{op=>add,in=>{1,3}}),
+ #{id:=5, res:=Big} = M5 = call(Pid, M4#{op=>sub,in=>{1 bsl 65, 3}}),
+ #{id:=6, res:=B3} = M6 = call(Pid, M5#{op=>"append",in=>{B1,B2}}),
+ #{id:=7, res:=4} = _ = call(Pid, M6#{op=>add,in=>{1,3}}),
+
+
+ %% update old maps and check id update
+ #{id:=2, res:=B3} = call(Pid, M1#{op=>"append",in=>{B1,B2}}),
+ #{id:=5, res:=99} = call(Pid, M4#{op=>add,in=>{33, 66}}),
+
+ %% cleanup
+ done = call(Pid, done),
+ ok.
+
+call(Pid, M) ->
+ Pid ! {self(), M}, receive {Pid, Res} -> Res end.
+
+guard_receive_loop() ->
+ receive
+ {Pid, #{ id:=Id, op:="append", in:={X,Y}}=M} when is_binary(X), is_binary(Y) ->
+ Pid ! {self(), M#{ id=>Id+1, res=><<X/binary,Y/binary>>}},
+ guard_receive_loop();
+ {Pid, #{ id:=Id, op:=add, in:={X,Y}}} ->
+ Pid ! {self(), #{ id=>Id+1, res=>X+Y}},
+ guard_receive_loop();
+ {Pid, #{ id:=Id, op:=sub, in:={X,Y}}=M} ->
+ Pid ! {self(), M#{ id=>Id+1, res=>X-Y}},
+ guard_receive_loop();
+ {Pid, #{ id:=Id, op:=idiv, in:={X,Y}}=M} ->
+ Pid ! {self(), M#{ id=>Id+1, res=>X div Y}},
+ guard_receive_loop();
+ {Pid, #{ id:=Id, op:=imul, in:={X,Y}}=M} ->
+ Pid ! {self(), M#{ id=>Id+1, res=>X * Y}},
+ guard_receive_loop();
+ {Pid, done} ->
+ Pid ! {self(), done};
+ {Pid, Other} ->
+ Pid ! {error, Other},
+ guard_receive_loop()
+ end.
+
+
+t_list_comprehension(Config) when is_list(Config) ->
+ [#{k:=1},#{k:=2},#{k:=3}] = [#{k=>I} || I <- [1,2,3]],
+ ok.
+
+t_guard_fun(Config) when is_list(Config) ->
+ F1 = fun
+ (#{s:=v,v:=V}) -> {v,V};
+ (#{s:=t,v:={V,V}}) -> {t,V};
+ (#{s:=l,v:=[V,V]}) -> {l,V}
+ end,
+
+ F2 = fun
+ (#{s:=T,v:={V,V}}) -> {T,V};
+ (#{s:=T,v:=[V,V]}) -> {T,V};
+ (#{s:=T,v:=V}) -> {T,V}
+ end,
+ V = <<"hi">>,
+
+ {v,V} = F1(#{s=>v,v=>V}),
+ {t,V} = F1(#{s=>t,v=>{V,V}}),
+ {l,V} = F1(#{s=>l,v=>[V,V]}),
+
+ {v,V} = F2(#{s=>v,v=>V}),
+ {t,V} = F2(#{s=>t,v=>{V,V}}),
+ {l,V} = F2(#{s=>l,v=>[V,V]}),
+
+ %% error case
+ {'EXIT', {function_clause,[{?MODULE,_,[#{s:=none,v:=none}],_}|_]}} = (catch F1(#{s=>none,v=>none})),
+ ok.
+
+
+t_map_sort_literals(Config) when is_list(Config) ->
+ % test relation
+
+ %% size order
+ true = #{ a => 1, b => 2} < id(#{ a => 1, b => 1, c => 1}),
+ true = #{ b => 1, a => 1} < id(#{ c => 1, a => 1, b => 1}),
+ false = #{ c => 1, b => 1, a => 1} < id(#{ c => 1, a => 1}),
+
+ %% key order
+ true = id(#{ a => 1 }) < id(#{ b => 1}),
+ false = id(#{ b => 1 }) < id(#{ a => 1}),
+ true = id(#{ a => 1, b => 1, c => 1 }) < id(#{ b => 1, c => 1, d => 1}),
+ true = id(#{ b => 1, c => 1, d => 1 }) > id(#{ a => 1, b => 1, c => 1}),
+ true = id(#{ c => 1, b => 1, a => 1 }) < id(#{ b => 1, c => 1, d => 1}),
+ true = id(#{ "a" => 1 }) < id(#{ <<"a">> => 1}),
+ false = id(#{ <<"a">> => 1 }) < id(#{ "a" => 1}),
+ false = id(#{ 1 => 1 }) < id(#{ 1.0 => 1}),
+ false = id(#{ 1.0 => 1 }) < id(#{ 1 => 1}),
+
+ %% value order
+ true = id(#{ a => 1 }) < id(#{ a => 2}),
+ false = id(#{ a => 2 }) < id(#{ a => 1}),
+ false = id(#{ a => 2, b => 1 }) < id(#{ a => 1, b => 3}),
+ true = id(#{ a => 1, b => 1 }) < id(#{ a => 1, b => 3}),
+
+ true = id(#{ "a" => "hi", b => 134 }) == id(#{ b => 134,"a" => "hi"}),
+
+ %% lists:sort
+
+ SortVs = [#{"a"=>1},#{a=>2},#{1=>3},#{<<"a">>=>4}],
+ [#{1:=ok},#{a:=ok},#{"a":=ok},#{<<"a">>:=ok}] = lists:sort([#{"a"=>ok},#{a=>ok},#{1=>ok},#{<<"a">>=>ok}]),
+ [#{1:=3},#{a:=2},#{"a":=1},#{<<"a">>:=4}] = lists:sort(SortVs),
+ [#{1:=3},#{a:=2},#{"a":=1},#{<<"a">>:=4}] = lists:sort(lists:reverse(SortVs)),
+
+ ok.
+
+t_warn_pair_key_overloaded(Config) when is_list(Config) ->
+ #{ "hi1" := 42 } = id(#{ "hi1" => 1, "hi1" => 42 }),
+
+ #{ "hi1" := 1337, "hi2" := [2], "hi3" := 3 } = id(#{
+ "hi1" => erlang:atom_to_binary(?MODULE,utf8),
+ "hi1" => erlang:binary_to_atom(<<"wazzup">>,utf8),
+ "hi1" => erlang:binary_to_float(<<"3.1416">>),
+ "hi1" => erlang:float_to_binary(3.1416),
+ "hi2" => erlang:pid_to_list(self()),
+ "hi3" => erlang:float_to_binary(3.1416),
+ "hi2" => lists:subtract([1,2],[1]),
+ "hi3" => +3,
+ "hi1" => erlang:min(1,2),
+ "hi1" => erlang:hash({1,2},35),
+ "hi1" => erlang:phash({1,2},33),
+ "hi1" => erlang:phash2({1,2},34),
+ "hi1" => erlang:integer_to_binary(1337),
+ "hi1" => erlang:binary_to_integer(<<"1337">>),
+ "hi4" => erlang:float_to_binary(3.1416)
+ }),
+ ok.
+
+t_warn_useless_build(Config) when is_list(Config) ->
+ [#{ a => id(I)} || I <- [1,2,3]],
+ ok.
+
+t_build_and_match_over_alloc(Config) when is_list(Config) ->
+ Ls = id([1,2,3]),
+ V0 = [a|Ls],
+ M0 = id(#{ "a" => V0 }),
+ #{ "a" := V1 } = M0,
+ V2 = id([c|Ls]),
+ M2 = id(#{ "a" => V2 }),
+ #{ "a" := V3 } = M2,
+ {[a,1,2,3],[c,1,2,3]} = id({V1,V3}),
+ ok.
+
+t_build_and_match_empty_val(Config) when is_list(Config) ->
+ F = fun(#{ "hi":=_,{1,2}:=_,1337:=_}) -> ok end,
+ ok = F(id(#{"hi"=>ok,{1,2}=>ok,1337=>ok})),
+
+ %% error case
+ {'EXIT',{function_clause,_}} = (catch (F(id(#{"hi"=>ok})))),
+ ok.
+
+t_build_and_match_val(Config) when is_list(Config) ->
+ F = fun
+ (#{ "hi" := first, v := V}) -> {1,V};
+ (#{ "hi" := second, v := V}) -> {2,V}
+ end,
+
+
+ {1,"hello"} = F(id(#{"hi"=>first,v=>"hello"})),
+ {2,"second"} = F(id(#{"hi"=>second,v=>"second"})),
+
+ %% error case
+ {'EXIT',{function_clause,_}} = (catch (F(id(#{"hi"=>ok})))),
+ ok.
+
+
+%% Use this function to avoid compile-time evaluation of an expression.
+id(I) -> I.
diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl
index 7186956603..16d15a59e5 100644
--- a/lib/compiler/test/warnings_SUITE.erl
+++ b/lib/compiler/test/warnings_SUITE.erl
@@ -390,6 +390,10 @@ effect(Config) when is_list(Config) ->
<<X:8>>;
unused_fun ->
fun() -> {ok,X} end;
+ unused_named_fun ->
+ fun F(0) -> 1;
+ F(N) -> N*F(N-1)
+ end;
unused_atom ->
ignore; %no warning
unused_nil ->
@@ -484,8 +488,9 @@ effect(Config) when is_list(Config) ->
{22,sys_core_fold,{no_effect,{erlang,is_integer,1}}},
{24,sys_core_fold,useless_building},
{26,sys_core_fold,useless_building},
- {32,sys_core_fold,{no_effect,{erlang,'=:=',2}}},
- {34,sys_core_fold,{no_effect,{erlang,get_cookie,0}}}]}}],
+ {28,sys_core_fold,useless_building},
+ {36,sys_core_fold,{no_effect,{erlang,'=:=',2}}},
+ {38,sys_core_fold,{no_effect,{erlang,get_cookie,0}}}]}}],
?line [] = run(Config, Ts),
ok.
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index 21cdedc156..5692138a8a 100644
--- a/lib/ssh/src/ssh_file.erl
+++ b/lib/ssh/src/ssh_file.erl
@@ -65,7 +65,7 @@ is_auth_key(Key, User,Opts) ->
%% Used by client
is_host_key(Key, PeerName, Algorithm, Opts) ->
- case lookup_host_key(PeerName, Algorithm, Opts) of
+ case lookup_host_key(Key, PeerName, Algorithm, Opts) of
{ok, Key} ->
true;
_ ->
@@ -121,9 +121,9 @@ decode_ssh_file(Pem, Password) ->
%% return {ok, Key(s)} or {error, not_found}
%%
-lookup_host_key(Host, Alg, Opts) ->
+lookup_host_key(KeyToMatch, Host, Alg, Opts) ->
Host1 = replace_localhost(Host),
- do_lookup_host_key(Host1, Alg, Opts).
+ do_lookup_host_key(KeyToMatch, Host1, Alg, Opts).
add_host_key(Host, Key, Opts) ->
@@ -204,10 +204,10 @@ replace_localhost("localhost") ->
replace_localhost(Host) ->
Host.
-do_lookup_host_key(Host, Alg, Opts) ->
+do_lookup_host_key(KeyToMatch, Host, Alg, Opts) ->
case file:open(file_name(user, "known_hosts", Opts), [read, binary]) of
{ok, Fd} ->
- Res = lookup_host_key_fd(Fd, Host, Alg),
+ Res = lookup_host_key_fd(Fd, KeyToMatch, Host, Alg),
file:close(Fd),
{ok, Res};
{error, enoent} -> {error, not_found};
@@ -228,16 +228,16 @@ identity_pass_phrase('ssh-rsa') ->
identity_pass_phrase("ssh-rsa") ->
rsa_pass_phrase.
-lookup_host_key_fd(Fd, Host, KeyType) ->
+lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) ->
case io:get_line(Fd, '') of
eof ->
{error, not_found};
Line ->
case ssh_decode_line(Line, known_hosts) of
[{Key, Attributes}] ->
- handle_host(Fd, Host, proplists:get_value(hostnames, Attributes), Key, KeyType);
+ handle_host(Fd, KeyToMatch, Host, proplists:get_value(hostnames, Attributes), Key, KeyType);
[] ->
- lookup_host_key_fd(Fd, Host, KeyType)
+ lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType)
end
end.
@@ -248,13 +248,13 @@ ssh_decode_line(Line, Type) ->
[]
end.
-handle_host(Fd, Host, HostList, Key, KeyType) ->
+handle_host(Fd, KeyToMatch, Host, HostList, Key, KeyType) ->
Host1 = host_name(Host),
- case lists:member(Host1, HostList) and key_match(Key, KeyType) of
- true ->
+ case lists:member(Host1, HostList) andalso key_match(Key, KeyType) of
+ true when KeyToMatch == Key ->
Key;
- false ->
- lookup_host_key_fd(Fd, Host, KeyType)
+ _ ->
+ lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType)
end.
host_name(Atom) when is_atom(Atom) ->
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index 01a0988718..fa4397b2f5 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -315,8 +315,8 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_DATA), ?UINT32(Recipient), ?UINT32(Len), Data:Le
recipient_channel = Recipient,
data = Data
};
-decode(<<?BYTE(?SSH_MSG_CHANNEL_EXTENDED_DATA), ?UINT32(Recipient),
- ?UINT32(DataType), Data/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_CHANNEL_EXTENDED_DATA), ?UINT32(Recipient),
+ ?UINT32(DataType), ?UINT32(Len), Data:Len/binary>>) ->
#ssh_msg_channel_extended_data{
recipient_channel = Recipient,
data_type_code = DataType,
diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl
index 1ffcf31134..4d6e88f58b 100644
--- a/lib/syntax_tools/src/erl_prettypr.erl
+++ b/lib/syntax_tools/src/erl_prettypr.erl
@@ -892,6 +892,32 @@ lay_2(Node, Ctxt) ->
beside(floating(text(".")), D2)),
maybe_parentheses(D3, Prec, Ctxt);
+ map_expr ->
+ {PrecL, Prec, _} = inop_prec('#'),
+ Ctxt1 = reset_prec(Ctxt),
+ D1 = par(seq(erl_syntax:map_expr_fields(Node),
+ floating(text(",")), Ctxt1, fun lay/2)),
+ D2 = beside(text("#{"), beside(D1, floating(text("}")))),
+ D3 = case erl_syntax:map_expr_argument(Node) of
+ none ->
+ D2;
+ A ->
+ beside(lay(A, set_prec(Ctxt, PrecL)), D2)
+ end,
+ maybe_parentheses(D3, Prec, Ctxt);
+
+ map_field_assoc ->
+ Ctxt1 = reset_prec(Ctxt),
+ D1 = lay(erl_syntax:map_field_assoc_name(Node), Ctxt1),
+ D2 = lay(erl_syntax:map_field_assoc_value(Node), Ctxt1),
+ par([D1, floating(text("=>")), D2], Ctxt1#ctxt.break_indent);
+
+ map_field_exact ->
+ Ctxt1 = reset_prec(Ctxt),
+ D1 = lay(erl_syntax:map_field_exact_name(Node), Ctxt1),
+ D2 = lay(erl_syntax:map_field_exact_value(Node), Ctxt1),
+ par([D1, floating(text(":=")), D2], Ctxt1#ctxt.break_indent);
+
rule ->
%% Comments on the name will be repeated; cf.
%% `function'.
diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl
index 93187fa018..2f4c9ac309 100644
--- a/lib/syntax_tools/src/erl_syntax.erl
+++ b/lib/syntax_tools/src/erl_syntax.erl
@@ -220,6 +220,16 @@
macro/2,
macro_arguments/1,
macro_name/1,
+ map_expr/1,
+ map_expr/2,
+ map_expr_argument/1,
+ map_expr_fields/1,
+ map_field_assoc/2,
+ map_field_assoc_name/1,
+ map_field_assoc_value/1,
+ map_field_exact/2,
+ map_field_exact_name/1,
+ map_field_exact_value/1,
match_expr/2,
match_expr_body/1,
match_expr_pattern/1,
@@ -580,11 +590,12 @@ type(Node) ->
{lc, _, _, _} -> list_comp;
{bc, _, _, _} -> binary_comp;
{match, _, _, _} -> match_expr;
+ {map, _, _, _} -> map_expr;
+ {map, _, _} -> map_expr;
+ {map_field_assoc, _, _, _} -> map_field_assoc;
+ {map_field_exact, _, _, _} -> map_field_exact;
{op, _, _, _, _} -> infix_expr;
{op, _, _, _} -> prefix_expr;
- {map,_,_} -> map;
- {map_field_assoc,_,_,_} -> map_field_assoc;
- {map_field_exact,_,_,_} -> map_field_exact;
{record, _, _, _, _} -> record_expr;
{record, _, _, _} -> record_expr;
{record_field, _, _, _, _} -> record_access;
@@ -1913,26 +1924,206 @@ atom_literal(Node) ->
%% =====================================================================
+%% @equiv map_expr(none, Fields)
-map_elements(Node) ->
+-spec map_expr([syntaxTree()]) -> syntaxTree().
+
+map_expr(Fields) ->
+ map_expr(none, Fields).
+
+
+%% =====================================================================
+%% @doc Creates an abstract map expression. If `Fields' is
+%% `[F1, ..., Fn]', then if `Argument' is `none', the result represents
+%% "<code>#{<em>F1</em>, ..., <em>Fn</em>}</code>",
+%% otherwise it represents
+%% "<code><em>Argument</em>#{<em>F1</em>, ..., <em>Fn</em>}</code>".
+%%
+%% @see map_expr/1
+%% @see map_expr_argument/1
+%% @see map_expr_fields/1
+%% @see map_field_assoc/2
+%% @see map_field_exact/2
+
+-record(map_expr, {argument :: 'none' | syntaxTree(),
+ fields :: [syntaxTree()]}).
+
+%% `erl_parse' representation:
+%%
+%% {map, Pos, Fields}
+%% {map, Pos, Argument, Fields}
+
+-spec map_expr('none' | syntaxTree(), [syntaxTree()]) -> syntaxTree().
+
+map_expr(Argument, Fields) ->
+ tree(map_expr, #map_expr{argument = Argument, fields = Fields}).
+
+revert_map_expr(Node) ->
+ Pos = get_pos(Node),
+ Argument = map_expr_argument(Node),
+ Fields = map_expr_fields(Node),
+ case Argument of
+ none ->
+ {map, Pos, Fields};
+ _ ->
+ {map, Pos, Argument, Fields}
+ end.
+
+
+%% =====================================================================
+%% @doc Returns the argument subtree of a `map_expr' node, if any. If `Node'
+%% represents "<code>#{...}</code>", `none' is returned.
+%% Otherwise, if `Node' represents "<code><em>Argument</em>#{...}</code>",
+%% `Argument' is returned.
+%%
+%% @see map_expr/3
+
+-spec map_expr_argument(syntaxTree()) -> 'none' | syntaxTree().
+
+map_expr_argument(Node) ->
case unwrap(Node) of
- {map, _, List} ->
- List;
- Node1 ->
- data(Node1)
+ {map, _, _} ->
+ none;
+ {map, _, Argument, _} ->
+ Argument;
+ Node1 ->
+ (data(Node1))#map_expr.argument
end.
-map_field_elements({_,_,K,V}) ->
- [K,V].
-map(List) ->
- tree(map, List).
+%% =====================================================================
+%% @doc Returns the list of field subtrees of a `map_expr' node.
+%%
+%% @see map_expr/3
+
+-spec map_expr_fields(syntaxTree()) -> [syntaxTree()].
+
+map_expr_fields(Node) ->
+ case unwrap(Node) of
+ {map, _, Fields} ->
+ Fields;
+ {map, _, _, Fields} ->
+ Fields;
+ Node1 ->
+ (data(Node1))#map_expr.fields
+ end.
+
-map_field_assoc(List) ->
- tree(map_field_assoc, List).
+%% =====================================================================
+%% @doc Creates an abstract map assoc field. The result represents
+%% "<code><em>Name</em> => <em>Value</em></code>".
+%%
+%% @see map_field_assoc_name/1
+%% @see map_field_assoc_value/1
+%% @see map_expr/3
+
+-record(map_field_assoc, {name :: syntaxTree(), value :: syntaxTree()}).
+
+%% `erl_parse' representation:
+%%
+%% {map_field_assoc, Pos, Name, Value}
+
+-spec map_field_assoc(syntaxTree(), syntaxTree()) -> syntaxTree().
+
+map_field_assoc(Name, Value) ->
+ tree(map_field_assoc, #map_field_assoc{name = Name, value = Value}).
+
+revert_map_field_assoc(Node) ->
+ Pos = get_pos(Node),
+ Name = map_field_assoc_name(Node),
+ Value = map_field_assoc_value(Node),
+ {map_field_assoc, Pos, Name, Value}.
+
+
+%% =====================================================================
+%% @doc Returns the name subtree of a `map_field_assoc' node.
+%%
+%% @see map_field_assoc/2
+
+-spec map_field_assoc_name(syntaxTree()) -> syntaxTree().
+
+map_field_assoc_name(Node) ->
+ case Node of
+ {map_field_assoc, _, Name, _} ->
+ Name;
+ _ ->
+ (data(Node))#map_field_assoc.name
+ end.
+
+
+%% =====================================================================
+%% @doc Returns the value subtree of a `map_field_assoc' node.
+%%
+%% @see map_field_assoc/2
+
+-spec map_field_assoc_value(syntaxTree()) -> syntaxTree().
+
+map_field_assoc_value(Node) ->
+ case Node of
+ {map_field_assoc, _, _, Value} ->
+ Value;
+ _ ->
+ (data(Node))#map_field_assoc.name
+ end.
+
+
+%% =====================================================================
+%% @doc Creates an abstract map exact field. The result represents
+%% "<code><em>Name</em> := <em>Value</em></code>".
+%%
+%% @see map_field_exact_name/1
+%% @see map_field_exact_value/1
+%% @see map_expr/3
+
+-record(map_field_exact, {name :: syntaxTree(), value :: syntaxTree()}).
+
+%% `erl_parse' representation:
+%%
+%% {map_field_exact, Pos, Name, Value}
+
+-spec map_field_exact(syntaxTree(), syntaxTree()) -> syntaxTree().
+
+map_field_exact(Name, Value) ->
+ tree(map_field_exact, #map_field_exact{name = Name, value = Value}).
+
+revert_map_field_exact(Node) ->
+ Pos = get_pos(Node),
+ Name = map_field_exact_name(Node),
+ Value = map_field_exact_value(Node),
+ {map_field_exact, Pos, Name, Value}.
+
+
+%% =====================================================================
+%% @doc Returns the name subtree of a `map_field_exact' node.
+%%
+%% @see map_field_exact/2
+
+-spec map_field_exact_name(syntaxTree()) -> syntaxTree().
+
+map_field_exact_name(Node) ->
+ case Node of
+ {map_field_exact, _, Name, _} ->
+ Name;
+ _ ->
+ (data(Node))#map_field_exact.name
+ end.
+
+
+%% =====================================================================
+%% @doc Returns the value subtree of a `map_field_exact' node.
+%%
+%% @see map_field_exact/2
+
+-spec map_field_exact_value(syntaxTree()) -> syntaxTree().
+
+map_field_exact_value(Node) ->
+ case Node of
+ {map_field_exact, _, _, Value} ->
+ Value;
+ _ ->
+ (data(Node))#map_field_exact.name
+ end.
-map_field_exact(List) ->
- tree(map_field_exact, List).
%% =====================================================================
%% @doc Creates an abstract tuple. If `Elements' is
@@ -6117,6 +6308,12 @@ revert_root(Node) ->
revert_list(Node);
list_comp ->
revert_list_comp(Node);
+ map_expr ->
+ revert_map_expr(Node);
+ map_field_assoc ->
+ revert_map_field_assoc(Node);
+ map_field_exact ->
+ revert_map_field_exact(Node);
match_expr ->
revert_match_expr(Node);
module_qualifier ->
@@ -6358,6 +6555,19 @@ subtrees(T) ->
As ->
[[macro_name(T)], As]
end;
+ map_expr ->
+ case map_expr_argument(T) of
+ none ->
+ [map_expr_fields(T)];
+ V ->
+ [[V], map_expr_fields(T)]
+ end;
+ map_field_assoc ->
+ [[map_field_assoc_name(T)],
+ [map_field_assoc_value(T)]];
+ map_field_exact ->
+ [[map_field_exact_name(T)],
+ [map_field_exact_value(T)]];
match_expr ->
[[match_expr_pattern(T)],
[match_expr_body(T)]];
@@ -6421,12 +6631,6 @@ subtrees(T) ->
try_expr_clauses(T),
try_expr_handlers(T),
try_expr_after(T)];
- map ->
- [map_elements(T)];
- map_field_assoc ->
- [map_field_elements(T)];
- map_field_exact ->
- [map_field_elements(T)];
tuple ->
[tuple_elements(T)]
end
@@ -6502,6 +6706,10 @@ make_tree(list, [P, [S]]) -> list(P, S);
make_tree(list_comp, [[T], B]) -> list_comp(T, B);
make_tree(macro, [[N]]) -> macro(N);
make_tree(macro, [[N], A]) -> macro(N, A);
+make_tree(map_expr, [Fs]) -> map_expr(Fs);
+make_tree(map_expr, [[E], Fs]) -> map_expr(E, Fs);
+make_tree(map_field_assoc, [[K], [V]]) -> map_field_assoc(K, V);
+make_tree(map_field_exact, [[K], [V]]) -> map_field_exact(K, V);
make_tree(match_expr, [[P], [E]]) -> match_expr(P, E);
make_tree(named_fun_expr, [[N], C]) -> named_fun_expr(N, C);
make_tree(module_qualifier, [[M], [N]]) -> module_qualifier(M, N);
@@ -6522,10 +6730,7 @@ make_tree(record_index_expr, [[T], [F]]) ->
make_tree(rule, [[N], C]) -> rule(N, C);
make_tree(size_qualifier, [[N], [A]]) -> size_qualifier(N, A);
make_tree(try_expr, [B, C, H, A]) -> try_expr(B, C, H, A);
-make_tree(tuple, [E]) -> tuple(E);
-make_tree(map, [E]) -> map(E);
-make_tree(map_field_assoc, [E]) -> map_field_assoc(E);
-make_tree(map_field_exact, [E]) -> map_field_exact(E).
+make_tree(tuple, [E]) -> tuple(E).
%% =====================================================================
diff --git a/lib/test_server/src/ts.erl b/lib/test_server/src/ts.erl
index 189a71a8ce..11d6f7af4d 100644
--- a/lib/test_server/src/ts.erl
+++ b/lib/test_server/src/ts.erl
@@ -212,6 +212,12 @@ run_all(_Vars) ->
run_some([], _Opts) ->
ok;
+run_some([{Spec,Mod}|Specs], Opts) ->
+ case run(Spec, Mod, Opts) of
+ ok -> ok;
+ Error -> io:format("~p: ~p~n",[{Spec,Mod},Error])
+ end,
+ run_some(Specs, Opts);
run_some([Spec|Specs], Opts) ->
case run(Spec, Opts) of
ok -> ok;
@@ -263,8 +269,17 @@ run(List, Opts) when is_list(List), is_list(Opts) ->
run_some(List, Opts);
%% run/2
-%% Runs one test spec with Options
-run(Testspec, Config) when is_atom(Testspec), is_list(Config) ->
+%% Runs one test spec with list of suites or with options
+run(Testspec, ModsOrConfig) when is_atom(Testspec),
+ is_list(ModsOrConfig) ->
+ case is_list_of_suites(ModsOrConfig) of
+ false ->
+ run(Testspec, {config_list,ModsOrConfig});
+ true ->
+ run_some([{Testspec,M} || M <- ModsOrConfig],
+ [batch])
+ end;
+run(Testspec, {config_list,Config}) ->
Options=check_test_get_opts(Testspec, Config),
IsSmoke=proplists:get_value(smoke,Config),
File=atom_to_list(Testspec),
@@ -310,34 +325,85 @@ run(Testspec, Config) when is_atom(Testspec), is_list(Config) ->
run_test(File, [{spec,[Spec]}], Options);
%% Runs one module in a spec (interactive)
run(Testspec, Mod) when is_atom(Testspec), is_atom(Mod) ->
- run_test({atom_to_list(Testspec), Mod},
+ run_test({atom_to_list(Testspec),Mod},
[{suite,Mod}],
[interactive]).
%% run/3
%% Run one module in a spec with Config
-run(Testspec,Mod,Config) when is_atom(Testspec), is_atom(Mod), is_list(Config) ->
+run(Testspec, Mod, Config) when is_atom(Testspec),
+ is_atom(Mod),
+ is_list(Config) ->
Options=check_test_get_opts(Testspec, Config),
- run_test({atom_to_list(Testspec), Mod},
- [{suite,Mod}],
- Options);
-
-%% Runs one testcase in a module.
-run(Testspec, Mod, Case) when is_atom(Testspec), is_atom(Mod), is_atom(Case) ->
+ run_test({atom_to_list(Testspec),Mod},
+ [{suite,Mod}], Options);
+%% Run multiple modules with Config
+run(Testspec, Mods, Config) when is_atom(Testspec),
+ is_list(Mods),
+ is_list(Config) ->
+ run_some([{Testspec,M} || M <- Mods], Config);
+%% Runs one test case in a module.
+run(Testspec, Mod, Case) when is_atom(Testspec),
+ is_atom(Mod),
+ is_atom(Case) ->
Options=check_test_get_opts(Testspec, []),
- Args = [{suite,atom_to_list(Mod)},{testcase,atom_to_list(Case)}],
+ Args = [{suite,Mod},{testcase,Case}],
+ run_test(atom_to_list(Testspec), Args, Options);
+%% Runs one or more groups in a module.
+run(Testspec, Mod, Grs={group,_Groups}) when is_atom(Testspec),
+ is_atom(Mod) ->
+ Options=check_test_get_opts(Testspec, []),
+ Args = [{suite,Mod},Grs],
+ run_test(atom_to_list(Testspec), Args, Options);
+%% Runs one or more test cases in a module.
+run(Testspec, Mod, TCs={testcase,_Cases}) when is_atom(Testspec),
+ is_atom(Mod) ->
+ Options=check_test_get_opts(Testspec, []),
+ Args = [{suite,Mod},TCs],
run_test(atom_to_list(Testspec), Args, Options).
%% run/4
-%% Run one testcase in a module with Options.
+%% Run one test case in a module with Options.
run(Testspec, Mod, Case, Config) when is_atom(Testspec),
is_atom(Mod),
is_atom(Case),
is_list(Config) ->
Options=check_test_get_opts(Testspec, Config),
- Args = [{suite,atom_to_list(Mod)}, {testcase,atom_to_list(Case)}],
+ Args = [{suite,Mod},{testcase,Case}],
+ run_test(atom_to_list(Testspec), Args, Options);
+%% Run one or more test cases in a module with Options.
+run(Testspec, Mod, {testcase,Cases}, Config) when is_atom(Testspec),
+ is_atom(Mod) ->
+ run(Testspec, Mod, Cases, Config);
+run(Testspec, Mod, Cases, Config) when is_atom(Testspec),
+ is_atom(Mod),
+ is_list(Cases),
+ is_list(Config) ->
+ Options=check_test_get_opts(Testspec, Config),
+ Args = [{suite,Mod},Cases],
+ run_test(atom_to_list(Testspec), Args, Options);
+%% Run one or more groups in a module with Options.
+run(Testspec, Mod, Grs={group,_Groups}, Config) when is_atom(Testspec),
+ is_atom(Mod) ->
+ Options=check_test_get_opts(Testspec, Config),
+ Args = [{suite,Mod},Grs],
run_test(atom_to_list(Testspec), Args, Options).
+
+is_list_of_suites(List) ->
+ lists:all(fun(Suite) ->
+ S = if is_atom(Suite) -> atom_to_list(Suite);
+ true -> Suite
+ end,
+ try lists:last(string:tokens(S,"_")) of
+ "SUITE" -> true;
+ "suite" -> true;
+ _ -> false
+ catch
+ _:_ -> false
+ end
+ end, List).
+
%% Create a spec to skip all SUITES, this is used when the application
%% to be tested is not part of the OTP release to be tested.
create_skip_spec(Testspec, SuitesToSkip) ->