File 5609-v3_core-Handle-map-comprehensions.patch of Package erlang
From 0de6bc8226e78326373aabb93ea9654ed44bb837 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Tue, 17 Jan 2023 08:58:23 +0100
Subject: [PATCH 09/12] v3_core: Handle map comprehensions
---
lib/compiler/src/v3_core.erl | 191 ++++++++++++-----
lib/compiler/test/Makefile | 3 +
lib/compiler/test/bs_bincomp_SUITE.erl | 2 +
lib/compiler/test/lc_SUITE.erl | 8 +
lib/compiler/test/mc_SUITE.erl | 272 +++++++++++++++++++++++++
lib/compiler/test/record_SUITE.erl | 8 +
6 files changed, 430 insertions(+), 54 deletions(-)
create mode 100644 lib/compiler/test/mc_SUITE.erl
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index eb337672ff..cedb162eef 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -120,7 +120,8 @@
-record(itry, {anno=#a{},args,vars,body,evars,handler}).
-record(ifilter, {anno=#a{},arg}).
-record(igen, {anno=#a{},acc_pat,acc_guard,
- skip_pat,tail,tail_pat,arg}).
+ skip_pat,tail,tail_pat,arg,
+ refill={nomatch,ignore}}).
-record(isimple, {anno=#a{},term :: cerl:cerl()}).
-type iapply() :: #iapply{}.
@@ -691,6 +692,9 @@ expr({lc,L,E,Qs0}, St0) ->
lc_tq(L, E, Qs1, #c_literal{anno=lineno_anno(L, St1),val=[]}, St1);
expr({bc,L,E,Qs}, St) ->
bc_tq(L, E, Qs, St);
+expr({mc,L,E,Qs0}, St0) ->
+ {Qs1,St1} = preprocess_quals(L, Qs0, St0),
+ mc_tq(L, E, Qs1, #c_literal{anno=lineno_anno(L, St1),val=[]}, St1);
expr({tuple,L,Es0}, St0) ->
{Es1,Eps,St1} = safe_list(Es0, St0),
A = record_anno(L, St1),
@@ -1594,37 +1598,31 @@ fun_tq(Cs0, L, St0, NameInfo) ->
{Fun,[],St4}.
%% lc_tq(Line, Exp, [Qualifier], Mc, State) -> {LetRec,[PreExp],State}.
-%% This TQ from Simon PJ pp 127-138.
+%% This TQ from Simon PJ pp 127-138.
lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
acc_pat=AccPat,acc_guard=AccGuard,
skip_pat=SkipPat,tail=Tail,tail_pat=TailPat,
+ refill={RefillPat,RefillAction},
arg={Pre,Arg}}|Qs], Mc, St0) ->
{Name,St1} = new_fun_name("lc", St0),
LA = lineno_anno(Line, St1),
- LAnno = #a{anno=LA},
F = #c_var{anno=LA,name={Name,1}},
Nc = #iapply{anno=GAnno,op=F,args=[Tail]},
{[FcVar,Var],St2} = new_vars(2, St1),
Fc = bad_generator([FcVar], FcVar, Arg),
- SkipClause = #iclause{anno=#a{anno=[skip_clause,compiler_generated|LA]},
- pats=[SkipPat],guard=[],body=[Nc]},
- TailClause = #iclause{anno=LAnno,pats=[TailPat],guard=[],body=[Mc]},
- {Cs,St4} = case {AccPat,SkipPat} of
- {nomatch,nomatch} ->
- {[TailClause],St2};
- {nomatch,_} ->
- {[SkipClause,TailClause],St2};
- _ ->
- {Lc,Lps,St3} = lc_tq(Line, E, Qs, Nc, St2),
- AccClause = #iclause{anno=LAnno,pats=[AccPat],guard=AccGuard,
- body=Lps ++ [Lc]},
- {[AccClause,SkipClause,TailClause],St3}
- end,
+ SkipClause = make_clause([skip_clause,compiler_generated|LA],
+ SkipPat, [], [], [Nc]),
+ TailClause = make_clause(LA, TailPat, [], [], [Mc]),
+ {Lc,Lps,St3} = lc_tq(Line, E, Qs, Nc, St2),
+ AccClause = make_clause(LA, AccPat, [], AccGuard, Lps ++ [Lc]),
+ RefillClause = make_clause(LA, RefillPat, [], [], [RefillAction,Nc]),
+ Cs0 = [AccClause,SkipClause,TailClause,RefillClause],
+ Cs = [C || C <- Cs0, C =/= nomatch],
Fun = #ifun{anno=GAnno,id=[],vars=[Var],clauses=Cs,fc=Fc},
{#iletrec{anno=GAnno#a{anno=[list_comprehension|GA]},defs=[{{Name,1},Fun}],
body=Pre ++ [#iapply{anno=GAnno,op=F,args=[Arg]}]},
- [],St4};
+ [],St3};
lc_tq(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
filter_tq(Line, E, Filter, Mc, St, Qs, fun lc_tq/5);
lc_tq(Line, E0, [], Mc0, St0) ->
@@ -1659,6 +1657,7 @@ bc_tq(Line, Exp, Qs0, St0) ->
bc_tq1(Line, E, [#igen{anno=GAnno,
acc_pat=AccPat,acc_guard=AccGuard,
skip_pat=SkipPat,tail=Tail,tail_pat=TailPat,
+ refill={RefillPat,RefillAction},
arg={Pre,Arg}}|Qs], Mc, St0) ->
{Name,St1} = new_fun_name("lbc", St0),
LA = lineno_anno(Line, St1),
@@ -1669,30 +1668,23 @@ bc_tq1(Line, E, [#igen{anno=GAnno,
F = #c_var{anno=LA,name={Name,2}},
Nc = #iapply{anno=GAnno,op=F,args=[Tail,AccVar]},
Fc = bad_generator(FcVars, hd(FcVars), Arg),
- SkipClause = #iclause{anno=#a{anno=[compiler_generated,skip_clause|LA]},
- pats=[SkipPat,IgnoreVar],guard=[],body=[Nc]},
- TailClause = #iclause{anno=LAnno,pats=[TailPat,IgnoreVar],guard=[],
- body=[AccVar]},
- {Cs,St} = case {AccPat,SkipPat} of
- {nomatch,nomatch} ->
- {[TailClause],St4};
- {nomatch,_} ->
- {[SkipClause,TailClause],St4};
- {_,_} ->
- {Bc,Bps,St5} = bc_tq1(Line, E, Qs, AccVar, St4),
- Body = Bps ++ [#iset{var=AccVar,arg=Bc},Nc],
- AccClause = #iclause{anno=LAnno,pats=[AccPat,IgnoreVar],
- guard=AccGuard,body=Body},
- {[AccClause,SkipClause,TailClause],St5}
- end,
- Fun = #ifun{anno=LAnno,id=[],vars=Vars,clauses=Cs,fc=Fc},
+ SkipClause = make_clause([compiler_generated,skip_clause|LA],
+ SkipPat, [IgnoreVar], [], [Nc]),
+ TailClause = make_clause(LA, TailPat, [IgnoreVar], [], [AccVar]),
+ {Bc,Bps,St5} = bc_tq1(Line, E, Qs, AccVar, St4),
+ Body = Bps ++ [#iset{var=AccVar,arg=Bc},Nc],
+ AccClause = make_clause(LA, AccPat, [IgnoreVar], AccGuard, Body),
+ RefillClause = make_clause(LA, RefillPat, [AccVar], [], [RefillAction,Nc]),
+ Cs0 = [AccClause,SkipClause,TailClause,RefillClause],
+ Cs = [C || C <- Cs0, C =/= nomatch],
+ Fun = #ifun{anno=GAnno,id=[],vars=Vars,clauses=Cs,fc=Fc},
%% Inlining would disable the size calculation optimization for
%% bs_init_writable.
{#iletrec{anno=LAnno#a{anno=[list_comprehension,no_inline|LA]},
defs=[{{Name,2},Fun}],
body=Pre ++ [#iapply{anno=LAnno,op=F,args=[Arg,Mc]}]},
- [],St};
+ [],St5};
bc_tq1(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
filter_tq(Line, E, Filter, Mc, St, Qs, fun bc_tq1/5);
bc_tq1(_, {bin,Bl,Elements}, [], AccVar, St0) ->
@@ -1729,6 +1721,20 @@ bc_tq_build(Line, Pre0, #c_var{name=AccVar}, Elements0, St0) ->
Anno = Anno0#a{anno=[compiler_generated,single_use|A]},
{set_anno(E, Anno),Pre0++Pre,St}.
+mc_tq(Line, {map_field_assoc,Lf,K,V}, Qs, Mc, St0) ->
+ E = {tuple,Lf,[K,V]},
+ {Lc,Pre0,St1} = lc_tq(Line, E, Qs, Mc, St0),
+ {LcVar,St2} = new_var(St1),
+ Pre = Pre0 ++ [#iset{var=LcVar,arg=Lc}],
+ Call = #icall{module=#c_literal{val=maps},
+ name=#c_literal{val=from_list},
+ args=[LcVar]},
+ {Call,Pre,St2}.
+
+make_clause(_Anno, nomatch, _PatExtra, _Guard, _Body) ->
+ nomatch;
+make_clause(Anno, Pat, PatExtra, Guard, Body) ->
+ #iclause{anno=#a{anno=Anno},pats=[Pat|PatExtra],guard=Guard,body=Body}.
%% filter_tq(Line, Expr, Filter, Mc, State, [Qualifier], TqFun) ->
%% {Case,[PreExpr],State}.
@@ -1805,6 +1811,7 @@ preprocess_quals(_, [], St, Acc) ->
is_generator({generate,_,_,_}) -> true;
is_generator({b_generate,_,_,_}) -> true;
+is_generator({m_generate,_,_,_}) -> true;
is_generator(_) -> false.
%% Retrieve the annotation from an Erlang AST form.
@@ -1813,7 +1820,7 @@ is_generator(_) -> false.
get_qual_anno(Abstract) -> element(2, Abstract).
%%
-%% Generators are abstracted as sextuplets:
+%% Generators are abstracted as a record #igen{}:
%% - acc_pat is the accumulator pattern, e.g. [Pat|Tail] for Pat <- Expr.
%% - acc_guard is the list of guards immediately following the current
%% generator in the qualifier list input.
@@ -1823,6 +1830,8 @@ get_qual_anno(Abstract) -> element(2, Abstract).
%% generator input.
%% - tail_pat is the tail pattern, respectively [] and <<_/bitstring>> for list
%% and bit string generators.
+%% - refill is a pair {RefillPat,RefillAction}, used to refill the iterator
+%% argument (used by map generators).
%% - arg is a pair {Pre,Arg} where Pre is the list of expressions to be
%% inserted before the comprehension function and Arg is the expression
%% that it should be passed.
@@ -1837,22 +1846,13 @@ generator(Line, {generate,Lg,P0,E}, Gs, St0) ->
{Head,St1} = list_gen_pattern(P0, Line, St0),
{[Tail,Skip],St2} = new_vars(2, St1),
{Cg,St3} = lc_guard_tests(Gs, St2),
- {AccPat,SkipPat} = case Head of
- #c_var{} ->
- %% If the generator pattern is a variable, the
- %% pattern from the accumulator clause can be
- %% reused in the skip one. lc_tq and bc_tq1 takes
- %% care of dismissing the latter in that case.
- Cons = ann_c_cons(LA, Head, Tail),
- {Cons,Cons};
- nomatch ->
- %% If it never matches, there is no need for
- %% an accumulator clause.
- {nomatch,ann_c_cons(LA, Skip, Tail)};
- _ ->
- {ann_c_cons(LA, Head, Tail),
- ann_c_cons(LA, Skip, Tail)}
- end,
+ AccPat = case Head of
+ nomatch ->
+ nomatch;
+ _ ->
+ ann_c_cons(LA, Head, Tail)
+ end,
+ SkipPat = ann_c_cons(LA, Skip, Tail),
{Ce,Pre,St4} = safe(E, St3),
Gen = #igen{anno=#a{anno=GA},
acc_pat=AccPat,acc_guard=Cg,skip_pat=SkipPat,
@@ -1885,7 +1885,90 @@ generator(Line, {b_generate,Lg,P,E}, Gs, St0) ->
tail_pat=#c_var{name='_'},
arg={Pre,Ce}},
{Gen,St1}
- end.
+ end;
+generator(Line, {m_generate,Lg,{map_field_exact,_,K0,V0},E}, Gs, St0) ->
+ %% Consider this example:
+ %%
+ %% [{K,V} || K := V <- L].
+ %%
+ %% The following Core Erlang code will be generated:
+ %%
+ %% letrec
+ %% 'lc$^0'/1 =
+ %% fun (Iter0) ->
+ %% case Iter0 of
+ %% <{K,V,NextIter}> when 'true' ->
+ %% let <Tail> =
+ %% apply 'lc$^0'/1(NextIter)
+ %% in [{K,V}|Tail]
+ %% <{_K,_V,NextIter}> when 'true' ->
+ %% %% Skip clause; will be optimized away later
+ %% %% since there are no filters.
+ %% apply 'lc$^0'/1(NextIter)
+ %% <'none'> when 'true' ->
+ %% []
+ %% <Iter> when 'true' ->
+ %% let NextIter =
+ %% call 'erts_internal':'mc_refill'(Iter)
+ %% in apply 'lc$^0'/1(NextIter)
+ %% <Bad> when 'true' ->
+ %% %% Generated by lc_tq/5. Never reached;
+ %% %% will be optimized away.
+ %% call 'erlang':'error'({'bad_generator',Bad})
+ %% end
+ %% in let <Iter> =
+ %% case call 'erts_internal':'mc_iterator'(L) of
+ %% <[]> when 'true' ->
+ %% call 'erlang':'error'
+ %% ({'bad_generator',L})
+ %% <Iter0> when 'true' ->
+ %% Iter0
+ %% end
+ %% in apply 'lc$^0'/1(Iter0)
+ LA = lineno_anno(Line, St0),
+ GA = lineno_anno(Lg, St0),
+ {Pat,St1} = list_gen_pattern({cons,Lg,K0,V0}, Line, St0),
+ {[SkipK,SkipV,IterVar,OuterIterVar,_BadGenVar],St2} = new_vars(5, St1),
+ {Cg,St3} = lc_guard_tests(Gs, St2),
+ {Ce,Pre0,St4} = safe(E, St3),
+ AccPat = case Pat of
+ #c_cons{hd=K,tl=V} ->
+ #c_tuple{es=[K,V,IterVar]};
+ nomatch ->
+ nomatch
+ end,
+ SkipPat = #c_tuple{es=[SkipK,SkipV,IterVar]},
+
+ Refill = {SkipK,
+ #iset{var=IterVar,
+ arg=#icall{anno=#a{anno=GA},
+ module=#c_literal{val=erts_internal},
+ name=#c_literal{val=mc_refill},
+ args=[SkipK]}}},
+
+ InitIter = #icall{anno=#a{anno=GA},
+ module=#c_literal{val=erts_internal},
+ name=#c_literal{val=mc_iterator},
+ args=[Ce]},
+
+ BadGenerator = bad_generator([#c_literal{val=[]}], Ce,
+ #c_literal{val=[],anno=GA}),
+ BeforeFc = #iclause{anno=#a{anno=GA},
+ pats=[IterVar],
+ guard=[],
+ body=[IterVar]},
+ Before = #iset{var=OuterIterVar,
+ arg=#icase{args=[InitIter],
+ clauses=[BadGenerator],
+ fc=BeforeFc}},
+
+ Pre = Pre0 ++ [Before],
+ Gen = #igen{anno=#a{anno=GA},
+ acc_pat=AccPat,acc_guard=Cg,skip_pat=SkipPat,
+ tail=IterVar,tail_pat=#c_literal{anno=LA,val=none},
+ refill=Refill,
+ arg={Pre,OuterIterVar}},
+ {Gen,St4}.
append_tail_segment(Segs, St0) ->
{Var,St} = new_var(St0),
diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile
index 3a585fc10e..19720abe50 100644
--- a/lib/compiler/test/Makefile
+++ b/lib/compiler/test/Makefile
@@ -41,6 +41,7 @@ MODULES= \
map_SUITE \
match_SUITE \
maybe_SUITE \
+ mc_SUITE \
misc_SUITE \
overridden_bif_SUITE \
random_code_SUITE \
@@ -76,6 +77,7 @@ NO_OPT= \
map \
match \
maybe \
+ mc \
misc \
overridden_bif \
receive \
@@ -103,6 +105,7 @@ INLINE= \
map \
match \
maybe \
+ mc \
misc \
overridden_bif \
receive \
diff --git a/lib/compiler/test/bs_bincomp_SUITE.erl b/lib/compiler/test/bs_bincomp_SUITE.erl
index d6718c6e9c..b560203514 100644
--- a/lib/compiler/test/bs_bincomp_SUITE.erl
+++ b/lib/compiler/test/bs_bincomp_SUITE.erl
@@ -626,6 +626,8 @@ cs(Bin) ->
ok;
bs_bincomp_no_ssa_opt_SUITE ->
ok;
+ bs_bincomp_no_copt_SUITE ->
+ ok;
bs_bincomp_post_opt_SUITE ->
ok;
_ ->
diff --git a/lib/compiler/test/lc_SUITE.erl b/lib/compiler/test/lc_SUITE.erl
index 7d30f1c74f..ab683f4de1 100644
--- a/lib/compiler/test/lc_SUITE.erl
+++ b/lib/compiler/test/lc_SUITE.erl
@@ -124,6 +124,11 @@ basic(Config) when is_list(Config) ->
[{file,"bad_lc.erl"},{line,7}]}|_]}} =
(catch id(bad_generator_bc(a))),
+ {'EXIT',{{bad_generator,a},
+ [{?MODULE,_,_,
+ [{file,"bad_lc.erl"},{line,10}]}|_]}} =
+ (catch id(bad_generator_mc(a))),
+
%% List comprehensions with improper lists.
{'EXIT',{{bad_generator,d},
[{?MODULE,_,_,
@@ -273,3 +278,6 @@ bad_generator(List) -> %Line 2
bad_generator_bc(List) -> %Line 5
<< <<I:4>> || %Line 6
I <- List>>. %Line 7
+bad_generator_mc(List) -> %Line 8
+ #{I => ok || %Line 9
+ I <- List}. %Line 10
diff --git a/lib/compiler/test/mc_SUITE.erl b/lib/compiler/test/mc_SUITE.erl
new file mode 100644
index 0000000000..21aa2c017e
--- /dev/null
+++ b/lib/compiler/test/mc_SUITE.erl
@@ -0,0 +1,272 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Originally based on Per Gustafsson's test suite.
+%%
+
+-module(mc_SUITE).
+
+-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ basic/1,duplicate_keys/1,mixed/1,
+ shadow/1,bad_generators/1]).
+
+-include_lib("common_test/include/ct.hrl").
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group,p}].
+
+groups() ->
+ [{p,test_lib:parallel(),
+ [basic,
+ duplicate_keys,
+ mixed,
+ shadow,
+ bad_generators]}].
+
+init_per_suite(Config) ->
+ test_lib:recompile(?MODULE),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+basic(_Config) ->
+ mc_double(0),
+ mc_double(1),
+ mc_double(2),
+ mc_double(3),
+ mc_double(4),
+ mc_double(5),
+
+ mc_double(17),
+ mc_double(18),
+ mc_double(19),
+
+ mc_double(30),
+ mc_double(31),
+ mc_double(32),
+ mc_double(33),
+ mc_double(34),
+
+ mc_double(63),
+ mc_double(64),
+ mc_double(65),
+
+ mc_double(77),
+ mc_double(127),
+ mc_double(128),
+ mc_double(255),
+ mc_double(333),
+ mc_double(444),
+ mc_double(7777),
+ mc_double(8765),
+
+ %% Patterns that cannot possibly match.
+ #{} = #{K => V || K := [V]={V} <- id(#{a => [b]})},
+ #{} = #{K => V || [K] = 42 := V <- id(#{42 => whatever})},
+
+ %% Filtering.
+ Map = #{{a,1} => {a,b,c}, {b,42} => [1,2,3], c => [4,5,6], d => {x,y}},
+ [c, d] = lists:sort([K || K := _ <- Map, is_atom(K)]),
+ [{a,b,c}, [1,2,3]] = lists:sort([V || {_,_} := V <- Map]),
+ [1] = [H || {_,_} := [H|_] <- Map],
+ [c, {b,42}] = lists:sort([K || K := [_|_] <- Map]),
+
+ ok.
+
+mc_double(Size) ->
+ Seq = lists:seq(1, Size),
+ Map = #{{key,I} => I || I <- Seq},
+
+ MapDouble = #{K => 2 * V || K := V <- id(Map)},
+ MapDouble = maps:from_list([{{key,I}, 2 * I} || I <- Seq]),
+
+ OddKeys = lists:seq(1, Size, 2),
+ OddKeys = lists:sort([I || {key,I} := I <- Map,
+ I rem 2 =/= 0]),
+
+ OddMap = #{I => [] || {key,I} := I <- Map,
+ I rem 2 =/= 0},
+ OddMap = #{I => [] || {key,I} := I <- Map,
+ id(I) rem 2 =/= 0},
+ OddKeys = lists:sort(maps:keys(OddMap)),
+
+ %% Test that map comprehensions works on iterators.
+ test_iterator(Map, 0),
+ test_iterator(Map, map_size(Map) div 3),
+ test_iterator(Map, map_size(Map) div 2),
+ test_iterator(Map, map_size(Map)),
+
+ ok.
+
+test_iterator(Map, N) ->
+ Iter0 = maps:iterator(Map),
+ {First,Iter} = grab(N, Iter0, []),
+ All = [{K,V} || K := V <- Iter] ++ First,
+ Map = maps:from_list(All),
+ ok.
+
+grab(0, Iter, Acc) ->
+ {Acc,Iter};
+grab(N, Iter0, Acc) ->
+ case maps:next(Iter0) of
+ none ->
+ {Acc,Iter0};
+ {K,V,Iter} ->
+ grab(N - 1, Iter, [{K,V}|Acc])
+ end.
+
+duplicate_keys(_Config) ->
+ #{x := b} = #{V => K || {K,V} <- [{a, x}, {b, x}]},
+
+ #{a := 4, b := 4} =
+ #{K => V || K <- [a,b],
+ <<V>> <= <<1,2,3,4>>},
+ ok.
+
+mixed(_Config) ->
+ Map = id(#{1 => 10, 2 => 5, 3 => 88, 4 => 99, 5 => 36}),
+ Bin = << <<K:8,V:24>> || K := V <- Map >>,
+ Map = maps:from_list([{K,V} || <<K:8,V:24>> <= Bin]),
+
+ Atoms = [list_to_atom([C]) || C <- lists:seq($a, $z)],
+ Integers = lists:seq(1, 64),
+
+ mixed_1(Atoms, Integers),
+ mixed_2(Atoms, Integers),
+ mixed_3(Atoms, Integers),
+
+ sum_of_triangular_numbers(7),
+ sum_of_triangular_numbers(10),
+
+ ok.
+
+mixed_1(Atoms, Integers) ->
+ IntegerMap = #{N => [] || N <- Integers},
+ IntegerKeys = [N || N := [] <- IntegerMap],
+ Integers = lists:sort(IntegerKeys),
+ Combined = [{C,N} || C <- Atoms, N := [] <- IntegerMap],
+ Combined = [{C,N} || C <- Atoms, N := [] <- maps:iterator(IntegerMap)],
+ Combined = [{C,N} || C <- Atoms, N <- IntegerKeys],
+
+ ok.
+
+mixed_2(Atoms, Integers) ->
+ IntegerMap = #{N => [] || N <- Integers},
+ IntegerKeys = [N || N := [] <- IntegerMap],
+ Bin = << <<N:16>> || N := [] <- IntegerMap >>,
+ Integers = lists:sort(IntegerKeys),
+
+ Combined = [{C,N} || N := [] <- IntegerMap, C <- Atoms],
+ Combined = [{C,N} || N := [] <- maps:iterator(IntegerMap), C <- Atoms],
+ Combined = [{C,N} || <<N:16>> <= Bin, C <- Atoms],
+ Combined = [{C,N} || N <- IntegerKeys, C <- Atoms],
+
+ ok.
+
+mixed_3(Atoms, Integers) ->
+ Map = #{K => V || {K,V} <- lists:zip(Atoms, Integers, trim)},
+ Bin = << <<N:16>> || _ := N <- Map >>,
+ {TrimmedAtoms,TrimmedIntegers} = lists:unzip([{K,V} || K := V <- Map]),
+
+ Combined = lists:sort([{K,V} || K := _ <- Map, _ := V <- Map]),
+ Combined = lists:sort([{K,V} || K <- TrimmedAtoms, <<V:16>> <= Bin]),
+ Combined = lists:sort([{K,V} || K <- TrimmedAtoms, V <- TrimmedIntegers]),
+
+ ok.
+
+sum_of_triangular_numbers(N) ->
+ Sum = N * (N + 1) * (N + 2) div 6,
+ Maps = [#{I => I || I <- lists:seq(0, I)} || I <- lists:seq(0, N)],
+ Numbers = [I || M <- Maps, I := I <- M],
+ Numbers = lists:flatten([[I || I := I <- M] || M <- Maps]),
+ Sum = lists:sum([lists:sum([I || I := I <- M]) || M <- Maps]),
+ Sum = lists:sum(Numbers),
+ ok.
+
+shadow(_Config)->
+ Shadowed = nomatch,
+ _ = id(Shadowed), %Eliminate warning.
+ Map = #{Shadowed => Shadowed+1 || Shadowed <- lists:seq(7, 9)},
+ #{7 := 8, 8 := 9, 9 := 10} = id(Map),
+ [8,9] = lists:sort([Shadowed || _ := Shadowed <- id(Map),
+ Shadowed < 10]),
+ ok.
+
+bad_generators(_Config) ->
+ %% Make sure that line numbers point out the generator.
+ case ?MODULE of
+ mc_inline_SUITE ->
+ ok;
+ _ ->
+ {'EXIT',{{bad_generator,a},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,4}]}|_]}} =
+ catch id(bad_generator(a)),
+
+ {'EXIT',{{bad_generator,a},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,7}]}|_]}} =
+ catch id(bad_generator_bc(a)),
+
+ {'EXIT',{{bad_generator,a},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,10}]}|_]}} =
+ catch id(bad_generator_mc(a)),
+
+ BadIterator = [16#ffff|#{}],
+
+ {'EXIT',{{bad_generator,BadIterator},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,4}]}|_]}} =
+ catch id(bad_generator(BadIterator)),
+
+ {'EXIT',{{bad_generator,BadIterator},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,7}]}|_]}} =
+ catch id(bad_generator_bc(BadIterator)),
+
+ {'EXIT',{{bad_generator,BadIterator},
+ [{?MODULE,_,_,
+ [{file,"bad_mc.erl"},{line,10}]}|_]}} =
+ catch id(bad_generator_mc(BadIterator))
+ end,
+ ok.
+
+id(I) -> I.
+
+-file("bad_mc.erl", 1).
+bad_generator(Map) -> %Line 2
+ [{K,V} || %Line 3
+ K := V <- Map]. %Line 4
+bad_generator_bc(Map) -> %Line 5
+ << <<K:8,V:24>> || %Line 6
+ K := V <- Map>>. %Line 7
+bad_generator_mc(Map) -> %Line 8
+ #{V => K || %Line 9
+ K := V <- Map}. %Line 10
diff --git a/lib/compiler/test/record_SUITE.erl b/lib/compiler/test/record_SUITE.erl
index f6c6c27965..34708a4d6d 100644
--- a/lib/compiler/test/record_SUITE.erl
+++ b/lib/compiler/test/record_SUITE.erl
@@ -238,6 +238,14 @@ record_test_2(Config) when is_list(Config) ->
begin not is_record(X, foo) or
is_reference(X) end],
+ Map = id(#{a => 1, b => #foo{a=2}, c => 3, d => #bar{d=4},
+ e => 5, f => #foo{a=6}, h => 7}),
+ [#foo{a=2},#foo{a=6}] = lists:sort([X || _ := X <- Map, is_record(X, foo)]),
+ [#bar{d=4}] = [X || _ := X <- Map, is_record(X, bar)],
+ [1,3,5,7,#foo{a=2},#foo{a=6}] =
+ lists:sort([X || _ := X <- Map, not is_record(X, bar)]),
+ [2,6] = lists:sort([A || _ := #foo{a=A} <- Map]),
+
%% Call is_record/2 with illegal arguments.
[] = [X || X <- [], is_record(t, id(X))],
{'EXIT',{badarg,_}} = (catch [X || X <- [1], is_record(t, id(X))]),
--
2.35.3