diff options
Diffstat (limited to 'lib/compiler')
32 files changed, 1201 insertions, 281 deletions
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml index cb39b28d3d..84ebd2f210 100644 --- a/lib/compiler/doc/src/notes.xml +++ b/lib/compiler/doc/src/notes.xml @@ -31,6 +31,301 @@ <p>This document describes the changes made to the Compiler application.</p> +<section><title>Compiler 5.0.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Named funs with the same name and arity could get mixed + up with each other.</p> + <p> + Own Id: OTP-12262</p> + </item> + <item> + <p> + Coalesce map keys in dialyzer mode</p> + <p> + This fixes a regression introduced in commit + 805f9c89fc01220bc1bb0f27e1b68fd4eca688ba The problem + occurred with compounded map keys compiled with dialyzer + option turned on, '+dialyzer'.</p> + <p> + Reported by: Ivan Uemlianin</p> + <p> + Own Id: OTP-12347</p> + </item> + </list> + </section> + +</section> + +<section><title>Compiler 5.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Corrected a bug with incorrect code generation when + inlining was turned on.</p> + <p> + Own Id: OTP-12132</p> + </item> + </list> + </section> + +</section> + +<section><title>Compiler 5.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + A Dialyzer crash involving analysis of Map types has now + been fixed.</p> + <p> + Own Id: OTP-11947</p> + </item> + <item> + <p>The compiler would fail to compile a file with a + latin-1 character in the false branch of an <c>-ifdef</c> + or <c>-indef</c>.</p> + <p> + Own Id: OTP-11987</p> + </item> + </list> + </section> + +</section> + +<section><title>Compiler 5.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Line numbers would not be correct when a binary + construction such as + '<c><<Bin/binary,...>></c>' fails. (Thanks to + Stanislav Seletskiy for reporting this bug.)</p> + <p> + Own Id: OTP-11572</p> + </item> + <item> + <p> + The compiler now properly annotates the code in value in + the '<c>after</c>' clause for a '<c>try</c>' so that + Dialyzer no longer generates a false warning for an + unmatched return.</p> + <p> + Own Id: OTP-11580</p> + </item> + <item> + <p> + Some case statements where no clause would match could + cause an internal error in the compiler. (Thanks to Erik + Soe Sorensen for reporting this bug.)</p> + <p> + Own Id: OTP-11610</p> + </item> + <item> + <p> + With <c>--Wunmatched_returns</c>, dialyzer will no longer + warn when the value of a list comprehension is ignored, + provided that the each value in the list would be an + atomic value (such as integer or atoms, as opposed to + tuples and lists). Example: ignoring '<c>[io:format(...) + || ...]</c>' will not cause a warning, while ignoring + '<c>[file:close(Fd) || ...]</c>' will.</p> + <p> + Own Id: OTP-11626</p> + </item> + <item> + <p> + Matching out a binary and applying the binary as if it + were a fun would crash the run-time system. (Thanks to + Loïc Hoguin.)</p> + <p> + Own Id: OTP-11672</p> + </item> + <item> + <p> + Some local implementations of removing the last element + from a list are replaced by <c>lists:droplast/1</c>. Note + that this requires at least <c>stdlib-2.0</c>, which is + the stdlib version delivered in OTP 17.0. (Thanks to Hans + Svensson)</p> + <p> + Own Id: OTP-11678</p> + </item> + <item> + <p> + Allow all auto imports to be suppressed at once. + Introducing the no_auto_import attribute: + -compile(no_auto_import). Useful for code generation + tools that always use the qualified function names and + want to avoid the auto imported functions clashing with + local ones. (Thanks to José Valim.)</p> + <p> + Own Id: OTP-11682</p> + </item> + <item> + <p> + Application upgrade (appup) files are corrected for the + following applications: </p> + <p> + <c>asn1, common_test, compiler, crypto, debugger, + dialyzer, edoc, eldap, erl_docgen, et, eunit, gs, hipe, + inets, observer, odbc, os_mon, otp_mibs, parsetools, + percept, public_key, reltool, runtime_tools, ssh, + syntax_tools, test_server, tools, typer, webtool, wx, + xmerl</c></p> + <p> + A new test utility for testing appup files is added to + test_server. This is now used by most applications in + OTP.</p> + <p> + (Thanks to Tobias Schlager)</p> + <p> + Own Id: OTP-11744</p> + </item> + <item> + <p> + Adapt 'asm' deprecation message to new version scheme. + (Thanks to Tuncer Ayaz)</p> + <p> + Own Id: OTP-11751</p> + </item> + <item> + <p> + A number of compiler errors where unusual or nonsensical + code would crash the compiler have been reported by Ulf + Norell and corrected by Anthony Ramine.</p> + <p> + Own Id: OTP-11770</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Compilation times for modules with a huge number for + record accesses using the dot operator has been improved.</p> + <p> + Own Id: OTP-10652</p> + </item> + <item> + <p> + The compiler can generate somewhat better code by moving + let expressions into sequences. (Thanks to Anthony + Ramine.)</p> + <p> + Own Id: OTP-11056</p> + </item> + <item> + <p> + Forbid unsized fields in patterns of binary generators + and simplified v3_core's translation of bit string + generators. (Thanks to Anthony Ramine.)</p> + <p> + Own Id: OTP-11186</p> + </item> + <item> + <p> + Funs can now be a given a name. Thanks to to Richard + O'Keefe for the idea (EEP37) and to Anthony Ramine for + the implementation.</p> + <p> + Own Id: OTP-11537</p> + </item> + <item> + <p> + Using the <c>from_asm</c> option to produce a BEAM file + starting from BEAM assembly code would often fail because + early optimization passes would not understand + instructions that later optimization passes would + introduce. (Thanks to Anthony Ramine.)</p> + <p> + Own Id: OTP-11544</p> + </item> + <item> + <p> + The <c>.core</c> and <c>.S</c> extensions are now + documented in the <c>erlc</c> documentation, and the + '<c>from_core</c>' and '<c>from_asm</c>' options are now + documented in the compiler documentation. (Thanks to + Tuncer Ayaz.)</p> + <p> + Own Id: OTP-11547</p> + </item> + <item> + <p>Optimization of case expressions that build tuples or + lists have been improved.</p> + <p> + Own Id: OTP-11584</p> + </item> + <item> + <p> + EEP43: New data type - Maps</p> + <p> + With Maps you may for instance: <taglist> <item><c>M0 = + #{ a => 1, b => 2}, % create + associations</c></item> <item><c>M1 = M0#{ a := 10 }, % + update values</c></item> <item><c>M2 = M1#{ "hi" => + "hello"}, % add new associations</c></item> <item><c>#{ + "hi" := V1, a := V2, b := V3} = M2. % match keys with + values</c></item> </taglist></p> + <p> + For information on how to use Maps please see Map Expressions in the + <seealso marker="doc/reference_manual:expressions#map_expressions"> + Reference Manual</seealso>.</p> + <p> + The current implementation is without the following + features: <taglist> <item>No variable keys</item> + <item>No single value access</item> <item>No map + comprehensions</item> </taglist></p> + <p> + Note that Maps is <em>experimental</em> during OTP 17.0.</p> + <p> + Own Id: OTP-11616</p> + </item> + <item> + <p> + Some function specs are corrected or moved and some edoc + comments are corrected in order to allow use of edoc. + (Thanks to Pierre Fenoll)</p> + <p> + Own Id: OTP-11702</p> + </item> + <item> + <p> + Thanks to Anthony Ramine for several improvements to the + optimizations in the BEAM compiler and for cleaning up + the code the code that transforms list and binary + comprehensions to Core Erlang.</p> + <p> + Own Id: OTP-11720</p> + </item> + <item> + <p> + The default encoding for Erlang source files is now + UTF-8. As a temporary measure to ease the transition from + the old default of latin-1, if the compiler encounters + byte sequences that are not valid UTF-8 sequences, the + compiler will re-try the compilation in latin-1 mode. + This workaround will be removed in a future release.</p> + <p> + Own Id: OTP-11791</p> + </item> + </list> + </section> + +</section> + <section><title>Compiler 4.9.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/compiler/src/beam_bool.erl b/lib/compiler/src/beam_bool.erl index d01f9ee13d..a452d30b61 100644 --- a/lib/compiler/src/beam_bool.erl +++ b/lib/compiler/src/beam_bool.erl @@ -126,44 +126,53 @@ bopt_block(Reg, Fail, OldIs, [{block,Bl0}|Acc0], St0) -> %% There was a reference to a boolean expression %% from inside a protected block (try/catch), to %% a boolean expression outside. - throw:protected_barrier -> + throw:protected_barrier -> failed; - %% The 'xor' operator was used. We currently don't - %% find it worthwile to translate 'xor' operators - %% (the code would be clumsy). - throw:'xor' -> + %% The 'xor' operator was used. We currently don't + %% find it worthwile to translate 'xor' operators + %% (the code would be clumsy). + throw:'xor' -> failed; - %% The block does not contain a boolean expression, - %% but only a call to a guard BIF. - %% For instance: ... when element(1, T) -> - throw:not_boolean_expr -> + %% The block does not contain a boolean expression, + %% but only a call to a guard BIF. + %% For instance: ... when element(1, T) -> + throw:not_boolean_expr -> failed; - %% The block contains a 'move' instruction that could - %% not be handled. - throw:move -> + %% The block contains a 'move' instruction that could + %% not be handled. + throw:move -> failed; - %% The optimization is not safe. (A register - %% used by the instructions following the - %% optimized code is either not assigned a - %% value at all or assigned a different value.) - throw:all_registers_not_killed -> + %% The optimization is not safe. (A register + %% used by the instructions following the + %% optimized code is either not assigned a + %% value at all or assigned a different value.) + throw:all_registers_not_killed -> failed; - throw:registers_used -> + throw:registers_used -> failed; - %% A protected block refered to the value - %% returned by another protected block, - %% probably because the Core Erlang code - %% used nested try/catches in the guard. - %% (v3_core never produces nested try/catches - %% in guards, so it must have been another - %% Core Erlang translator.) - throw:protected_violation -> + %% A protected block refered to the value + %% returned by another protected block, + %% probably because the Core Erlang code + %% used nested try/catches in the guard. + %% (v3_core never produces nested try/catches + %% in guards, so it must have been another + %% Core Erlang translator.) + throw:protected_violation -> + failed; + + %% Failed to work out the live registers for a GC + %% BIF. For example, if the number of live registers + %% needed to be 4 because {x,3} was a source register, + %% but {x,2} was not known to be initialized, this + %% exception would be thrown. + throw:gc_bif_alloc_failure -> failed + end end. @@ -438,9 +447,10 @@ bopt_bool_args(As, Forest) -> mapfoldl(fun bopt_bool_arg/2, Forest, As). bopt_bool_arg({T,_}=R, Forest) when T =:= x; T =:= y; T =:= tmp -> - Val = case gb_trees:get(R, Forest) of - any -> {test,is_eq_exact,fail,[R,{atom,true}]}; - Val0 -> Val0 + Val = case gb_trees:lookup(R, Forest) of + {value,any} -> {test,is_eq_exact,fail,[R,{atom,true}]}; + {value,Val0} -> Val0; + none -> throw(mixed) end, {Val,gb_trees:delete(R, Forest)}; bopt_bool_arg(Term, Forest) -> @@ -664,10 +674,16 @@ put_reg_1(V, [], I) -> [{I,V}]. fetch_reg(V, [{I,V}|_]) -> {x,I}; fetch_reg(V, [_|SRs]) -> fetch_reg(V, SRs). -live_regs(Regs) -> - foldl(fun ({I,_}, _) -> - I - end, -1, Regs)+1. +live_regs([{_,reserved}|_]) -> + %% We are not sure that this register is initialized, so we must + %% abort the optimization. + throw(gc_bif_alloc_failure); +live_regs([{I,_}]) -> + I+1; +live_regs([{_,_}|Regs]) -> + live_regs(Regs); +live_regs([]) -> + 0. %%% diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl index 4bdfe4e0c2..c45596f236 100644 --- a/lib/compiler/src/beam_disasm.erl +++ b/lib/compiler/src/beam_disasm.erl @@ -1030,6 +1030,7 @@ resolve_inst({gc_bif2,Args},Imports,_,_) -> [F,Live,Bif,A1,A2,Reg] = resolve_args(Args), {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports), {gc_bif,BifName,F,Live,[A1,A2],Reg}; + %% %% New instruction in R14, gc_bif with 3 arguments %% @@ -1146,21 +1147,17 @@ resolve_inst({put_map_assoc,Args},_,_,_) -> [FLbl,Src,Dst,{u,N},{{z,1},{u,_Len},List0}] = Args, List = resolve_args(List0), {put_map_assoc,FLbl,Src,Dst,N,{list,List}}; - resolve_inst({put_map_exact,Args},_,_,_) -> [FLbl,Src,Dst,{u,N},{{z,1},{u,_Len},List0}] = Args, List = resolve_args(List0), {put_map_exact,FLbl,Src,Dst,N,{list,List}}; - -resolve_inst({is_map,Args0},_,_,_) -> +resolve_inst({is_map=I,Args0},_,_,_) -> [FLbl|Args] = resolve_args(Args0), - {test, is_map, FLbl, Args}; - + {test,I,FLbl,Args}; resolve_inst({has_map_fields,Args0},_,_,_) -> [FLbl,Src,{{z,1},{u,_Len},List0}] = Args0, List = resolve_args(List0), {test,has_map_fields,FLbl,Src,{list,List}}; - resolve_inst({get_map_elements,Args0},_,_,_) -> [FLbl,Src,{{z,1},{u,_Len},List0}] = Args0, List = resolve_args(List0), diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl index 3c121f3b04..9d6768b157 100644 --- a/lib/compiler/src/cerl.erl +++ b/lib/compiler/src/cerl.erl @@ -124,21 +124,19 @@ %% keep map exports here for now map_es/1, - map_val/1, + map_arg/1, update_c_map/3, - ann_c_map/3, + c_map/1, is_c_map_empty/1, + ann_c_map/2, ann_c_map/3, map_pair_op/1,map_pair_key/1,map_pair_val/1, update_c_map_pair/4, + c_map_pair/2, ann_c_map_pair/4 ]). --export_type([c_binary/0, c_call/0, c_clause/0, c_cons/0, c_fun/0, c_literal/0, - c_module/0, c_tuple/0, c_values/0, c_var/0, cerl/0, var_name/0]). - -%% -%% needed by the include file below -- do not move -%% --type var_name() :: integer() | atom() | {atom(), integer()}. +-export_type([c_binary/0, c_bitstr/0, c_call/0, c_clause/0, c_cons/0, c_fun/0, + c_literal/0, c_map/0, c_map_pair/0, c_module/0, c_tuple/0, + c_values/0, c_var/0, cerl/0, var_name/0]). -include("core_parse.hrl"). @@ -173,6 +171,8 @@ | c_module() | c_primop() | c_receive() | c_seq() | c_try() | c_tuple() | c_values() | c_var(). +-type var_name() :: integer() | atom() | {atom(), integer()}. + %% ===================================================================== %% Representation (general) %% @@ -204,13 +204,15 @@ %% <td>call</td> %% <td>case</td> %% <td>catch</td> -%% </tr><tr> %% <td>clause</td> +%% </tr><tr> %% <td>cons</td> %% <td>fun</td> %% <td>let</td> %% <td>letrec</td> %% <td>literal</td> +%% <td>map</td> +%% <td>map_pair</td> %% <td>module</td> %% </tr><tr> %% <td>primop</td> @@ -261,10 +263,10 @@ %% @see subtrees/1 %% @see meta/1 --type ctype() :: 'alias' | 'apply' | 'binary' | 'bitrst' | 'call' | 'case' - | 'catch' | 'clause' | 'cons' | 'fun' | 'let' | 'letrec' - | 'literal' | 'map' | 'module' | 'primop' | 'receive' | 'seq' - | 'try' | 'tuple' | 'values' | 'var'. +-type ctype() :: 'alias' | 'apply' | 'binary' | 'bitrst' | 'call' | 'case' + | 'catch' | 'clause' | 'cons' | 'fun' | 'let' | 'letrec' + | 'literal' | 'map' | 'map_pair' | 'module' | 'primop' + | 'receive' | 'seq' | 'try' | 'tuple' | 'values' | 'var'. -spec type(cerl()) -> ctype(). @@ -1575,25 +1577,91 @@ ann_make_list(_, [], Node) -> %% --------------------------------------------------------------------- %% maps --spec map_es(c_map()) -> [cerl()]. +-spec map_es(c_map()) -> [c_map_pair()]. map_es(#c_map{es = Es}) -> Es. --spec map_val(c_map()) -> cerl(). -map_val(#c_map{var = M}) -> +-spec map_arg(c_map()) -> c_map() | c_literal(). + +map_arg(#c_map{arg=M}) -> M. +-spec c_map([c_map_pair()]) -> c_map(). + +c_map(Pairs) -> + #c_map{es=Pairs}. + +-spec is_c_map_empty(c_map() | c_literal()) -> boolean(). + +is_c_map_empty(#c_map{ es=[] }) -> true; +is_c_map_empty(#c_literal{val=M}) when is_map(M),map_size(M) =:= 0 -> true; +is_c_map_empty(_) -> false. + +-spec ann_c_map([term()], [cerl()]) -> c_map() | c_literal(). + +ann_c_map(As,Es) -> + ann_c_map(As, #c_literal{val=#{}}, Es). + +-spec ann_c_map([term()], c_map() | c_literal(), [c_map_pair()]) -> c_map() | c_literal(). + +ann_c_map(As,#c_literal{val=Mval}=M,Es) when is_map(Mval), map_size(Mval) =:= 0 -> + Pairs = [[Ck,Cv]||#c_map_pair{key=Ck,val=Cv}<-Es], + IsLit = lists:foldl(fun(Pair,Res) -> + Res andalso is_lit_list(Pair) + end, true, Pairs), + Fun = fun(Pair) -> [K,V] = lit_list_vals(Pair), {K,V} end, + case IsLit of + false -> + #c_map{arg=M, es=Es, anno=As }; + true -> + #c_literal{anno=As, val=maps:from_list(lists:map(Fun, Pairs))} + end; +ann_c_map(As,#c_literal{val=M},Es) when is_map(M) -> + fold_map_pairs(As,Es,M); ann_c_map(As,M,Es) -> - #c_map{var=M,es = Es, anno = As }. + #c_map{arg=M, es=Es, anno=As }. + +fold_map_pairs(As,[],M) -> #c_literal{anno=As,val=M}; +%% M#{ K => V} +fold_map_pairs(As,[#c_map_pair{op=#c_literal{val=assoc},key=Ck,val=Cv}=E|Es],M) -> + case is_lit_list([Ck,Cv]) of + true -> + [K,V] = lit_list_vals([Ck,Cv]), + fold_map_pairs(As,Es,maps:put(K,V,M)); + false -> + #c_map{arg=#c_literal{val=M,anno=As}, es=[E|Es], anno=As } + end; +%% M#{ K := V} +fold_map_pairs(As,[#c_map_pair{op=#c_literal{val=exact},key=Ck,val=Cv}=E|Es],M) -> + case is_lit_list([Ck,Cv]) of + true -> + [K,V] = lit_list_vals([Ck,Cv]), + case maps:is_key(K,M) of + true -> fold_map_pairs(As,Es,maps:put(K,V,M)); + false -> + #c_map{arg=#c_literal{val=M,anno=As}, es=[E|Es], anno=As } + end; + false -> + #c_map{arg=#c_literal{val=M,anno=As}, es=[E|Es], anno=As } + end; +fold_map_pairs(As,Es,M) -> + #c_map{arg=#c_literal{val=M,anno=As}, es=Es, anno=As }. + +%-spec update_c_map(c_map() | c_literal(), [c_map_pair()]) -> c_map() | c_literal(). update_c_map(Old,M,Es) -> - #c_map{var=M, es = Es, anno = get_ann(Old)}. + #c_map{arg=M, es = Es, anno = get_ann(Old)}. map_pair_key(#c_map_pair{key=K}) -> K. map_pair_val(#c_map_pair{val=V}) -> V. map_pair_op(#c_map_pair{op=Op}) -> Op. +-spec c_map_pair(cerl(), cerl()) -> c_map_pair(). + +c_map_pair(Key,Val) -> + #c_map_pair{op=#c_literal{val=assoc},key=Key,val=Val}. + -spec ann_c_map_pair([term()], cerl(), cerl(), cerl()) -> c_map_pair(). @@ -4195,6 +4263,9 @@ ann_make_tree(As, bitstr, [[V],[S],[U],[T],[Fs]]) -> ann_c_bitstr(As, V, S, U, T, Fs); ann_make_tree(As, cons, [[H], [T]]) -> ann_c_cons(As, H, T); ann_make_tree(As, tuple, [Es]) -> ann_c_tuple(As, Es); +ann_make_tree(As, map, [Es]) -> ann_c_map(As, Es); +ann_make_tree(As, map, [[A], Es]) -> ann_c_map(As, A, Es); +ann_make_tree(As, map_pair, [[Op], [K], [V]]) -> ann_c_map_pair(As, Op, K, V); ann_make_tree(As, 'let', [Vs, [A], [B]]) -> ann_c_let(As, Vs, A, B); ann_make_tree(As, seq, [[A], [B]]) -> ann_c_seq(As, A, B); ann_make_tree(As, apply, [[Op], Es]) -> ann_c_apply(As, Op, Es); @@ -4324,12 +4395,8 @@ meta_1(cons, Node) -> %% we get exactly one element, we generate a 'c_cons' call %% instead of 'make_list' to reconstruct the node. case split_list(Node) of - {[H], none} -> - meta_call(c_cons, [meta(H), meta(c_nil())]); {[H], Node1} -> meta_call(c_cons, [meta(H), meta(Node1)]); - {L, none} -> - meta_call(make_list, [make_list(meta_list(L))]); {L, Node1} -> meta_call(make_list, [make_list(meta_list(L)), meta(Node1)]) @@ -4416,8 +4483,6 @@ split_list(Node, L) -> case type(Node) of cons when A =:= [] -> split_list(cons_tl(Node), [cons_hd(Node) | L]); - nil when A =:= [] -> - {lists:reverse(L), none}; _ -> {lists:reverse(L), Node} end. diff --git a/lib/compiler/src/cerl_clauses.erl b/lib/compiler/src/cerl_clauses.erl index 76d70dcabf..87bd47c08b 100644 --- a/lib/compiler/src/cerl_clauses.erl +++ b/lib/compiler/src/cerl_clauses.erl @@ -356,14 +356,19 @@ match(P, E, Bs) -> end; map -> %% The most we can do is to say "definitely no match" if a - %% binary pattern is matched against non-binary data. + %% map pattern is matched against non-map data. case E of any -> {false, Bs}; _ -> case type(E) of - literal -> - none; + literal -> + case is_map(concrete(E)) of + false -> + none; + true -> + {false, Bs} + end; cons -> none; tuple -> diff --git a/lib/compiler/src/cerl_inline.erl b/lib/compiler/src/cerl_inline.erl index bc9bdc67a4..75740e8b9d 100644 --- a/lib/compiler/src/cerl_inline.erl +++ b/lib/compiler/src/cerl_inline.erl @@ -42,7 +42,7 @@ bitstr_flags/1, binary_segments/1, update_c_alias/3, update_c_apply/3, update_c_binary/2, update_c_bitstr/6, update_c_call/4, update_c_case/3, update_c_catch/2, - update_c_clause/4, c_fun/2, c_int/1, c_let/3, + update_c_clause/4, c_fun/2, c_int/1, c_let/3, ann_c_let/4, update_c_let/4, update_c_letrec/3, update_c_module/5, update_c_primop/3, update_c_receive/4, update_c_seq/3, c_seq/2, update_c_try/6, c_tuple/1, update_c_values/2, @@ -64,7 +64,7 @@ seq_body/1, set_ann/2, try_arg/1, try_body/1, try_vars/1, try_evars/1, try_handler/1, tuple_es/1, tuple_arity/1, type/1, values_es/1, var_name/1, - map_val/1, map_es/1, update_c_map/3, + map_arg/1, map_es/1, update_c_map/3, update_c_map_pair/4, map_pair_op/1, map_pair_key/1, map_pair_val/1 ]). @@ -1034,7 +1034,8 @@ i_apply(E, Ctxt, Ren, Env, S) -> E2 = case is_c_fname(E1) andalso length(Es) =/= fname_arity(E1) of true -> V = new_var(Env), - update_c_let(E, [V], E1, update_c_apply(E, V, Es)); + ann_c_let(get_ann(E), [V], E1, + update_c_apply(E, V, Es)); false -> update_c_apply(E, E1, Es) end, @@ -1342,7 +1343,7 @@ i_bitstr(E, Ren, Env, S) -> i_map(E, Ctx, Ren, Env, S) -> %% Visit the segments for value. - {M1, S1} = i(map_val(E), value, Ren, Env, S), + {M1, S1} = i(map_arg(E), value, Ren, Env, S), {Es, S2} = mapfoldl(fun (E, S) -> i_map_pair(E, Ctx, Ren, Env, S) end, S1, map_es(E)), @@ -1419,8 +1420,8 @@ i_pattern(E, Ren, Env, Ren0, Env0, S) -> S2 = count_size(weight(binary), S1), {update_c_binary(E, Es), S2}; map -> - %% map patterns should not have vals - M = map_val(E), + %% map patterns should not have args + M = map_arg(E), {Es, S1} = mapfoldl(fun (E, S) -> i_map_pair_pattern(E, Ren, Env, Ren0, Env0, S) diff --git a/lib/compiler/src/cerl_trees.erl b/lib/compiler/src/cerl_trees.erl index 2ebeab243f..e53bdd4efb 100644 --- a/lib/compiler/src/cerl_trees.erl +++ b/lib/compiler/src/cerl_trees.erl @@ -57,7 +57,7 @@ update_c_try/6, update_c_tuple/2, update_c_tuple_skel/2, update_c_values/2, values_es/1, var_name/1, - map_val/1, map_es/1, + map_arg/1, map_es/1, ann_c_map/3, update_c_map/3, map_pair_key/1,map_pair_val/1,map_pair_op/1, @@ -138,7 +138,7 @@ map_1(F, T) -> tuple -> update_c_tuple_skel(T, map_list(F, tuple_es(T))); map -> - update_c_map(T, map(F,map_val(T)), map_list(F, map_es(T))); + update_c_map(T, map(F, map_arg(T)), map_list(F, map_es(T))); map_pair -> update_c_map_pair(T, map(F, map_pair_op(T)), map(F, map_pair_key(T)), @@ -372,7 +372,7 @@ mapfold(F, S0, T) -> {Ts, S1} = mapfold_list(F, S0, tuple_es(T)), F(update_c_tuple_skel(T, Ts), S1); map -> - {M , S1} = mapfold(F, S0, map_val(T)), + {M , S1} = mapfold(F, S0, map_arg(T)), {Ts, S2} = mapfold_list(F, S1, map_es(T)), F(update_c_map(T, M, Ts), S2); map_pair -> @@ -724,7 +724,7 @@ label(T, N, Env) -> {As, N2} = label_ann(T, N1), {ann_c_tuple_skel(As, Ts), N2}; map -> - {M, N1} = label(map_val(T), N, Env), + {M, N1} = label(map_arg(T), N, Env), {Ts, N2} = label_list(map_es(T), N1, Env), {As, N3} = label_ann(T, N2), {ann_c_map(As, M, Ts), N3}; diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 9030dd998b..c7d91070f6 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -234,7 +234,9 @@ format_error({crash,Pass,Reason}) -> format_error({bad_return,Pass,Reason}) -> io_lib:format("internal error in ~p;\nbad return value: ~ts", [Pass,format_error_reason(Reason)]); format_error({module_name,Mod,Filename}) -> - io_lib:format("Module name '~s' does not match file name '~ts'", [Mod,Filename]). + io_lib:format("Module name '~s' does not match file name '~ts'", [Mod,Filename]); +format_error(reparsing_invalid_unicode) -> + "Non-UTF-8 character(s) detected, but no encoding declared. Encode the file in UTF-8 or add \"%% coding: latin-1\" at the beginning of the file. Retrying with latin-1 encoding.". format_error_reason({Reason, Stack}) when is_list(Stack) -> StackFun = fun @@ -792,20 +794,59 @@ no_native_compilation(BeamFile, #compile{options=Opts0}) -> _ -> false end. -parse_module(St) -> - Opts = St#compile.options, - Cwd = ".", - IncludePath = [Cwd, St#compile.dir|inc_paths(Opts)], - R = epp:parse_file(St#compile.ifile, IncludePath, pre_defs(Opts)), +parse_module(St0) -> + case do_parse_module(utf8, St0) of + {ok,_}=Ret -> + Ret; + {error,_}=Ret -> + Ret; + {invalid_unicode,File,Line} -> + case do_parse_module(latin1, St0) of + {ok,St} -> + Es = [{File,[{Line,?MODULE,reparsing_invalid_unicode}]}], + {ok,St#compile{warnings=Es++St#compile.warnings}}; + {error,St} -> + Es = [{File,[{Line,?MODULE,reparsing_invalid_unicode}]}], + {error,St#compile{errors=Es++St#compile.errors}} + end + end. + +do_parse_module(DefEncoding, #compile{ifile=File,options=Opts,dir=Dir}=St) -> + R = epp:parse_file(File, + [{includes,[".",Dir|inc_paths(Opts)]}, + {macros,pre_defs(Opts)}, + {default_encoding,DefEncoding}, + extra]), case R of - {ok,Forms} -> - Encoding = epp:read_encoding(St#compile.ifile), - {ok,St#compile{code=Forms,encoding=Encoding}}; + {ok,Forms,Extra} -> + Encoding = proplists:get_value(encoding, Extra), + case find_invalid_unicode(Forms, File) of + none -> + {ok,St#compile{code=Forms,encoding=Encoding}}; + {invalid_unicode,_,_}=Ret -> + case Encoding of + none -> + Ret; + _ -> + {ok,St#compile{code=Forms,encoding=Encoding}} + end + end; {error,E} -> Es = [{St#compile.ifile,[{none,?MODULE,{epp,E}}]}], {error,St#compile{errors=St#compile.errors ++ Es}} end. +find_invalid_unicode([H|T], File0) -> + case H of + {attribute,_,file,{File,_}} -> + find_invalid_unicode(T, File); + {error,{Line,file_io_server,invalid_unicode}} -> + {invalid_unicode,File0,Line}; + _Other -> + find_invalid_unicode(T, File0) + end; +find_invalid_unicode([], _) -> none. + parse_core(St) -> case file:read_file(St#compile.ifile) of {ok,Bin} -> diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src index 8775c84698..8f68915f8e 100644 --- a/lib/compiler/src/compiler.app.src +++ b/lib/compiler/src/compiler.app.src @@ -67,4 +67,6 @@ ]}, {registered, []}, {applications, [kernel, stdlib]}, - {env, []}]}. + {env, []}, + {runtime_dependencies, ["stdlib-2.0","kernel-3.0","hipe-3.10.3","erts-6.0", + "crypto-3.3"]}]}. diff --git a/lib/compiler/src/core_lib.erl b/lib/compiler/src/core_lib.erl index ed181e3baa..0d95971f91 100644 --- a/lib/compiler/src/core_lib.erl +++ b/lib/compiler/src/core_lib.erl @@ -59,7 +59,7 @@ is_lit_bin(Es) -> %% Return the value of LitExpr. -spec literal_value(cerl:c_literal() | cerl:c_binary() | - cerl:c_cons() | cerl:c_tuple()) -> term(). + cerl:c_map() | cerl:c_cons() | cerl:c_tuple()) -> term(). literal_value(#c_literal{val=V}) -> V; literal_value(#c_binary{segments=Es}) -> @@ -67,7 +67,14 @@ literal_value(#c_binary{segments=Es}) -> literal_value(#c_cons{hd=H,tl=T}) -> [literal_value(H)|literal_value(T)]; literal_value(#c_tuple{es=Es}) -> - list_to_tuple(literal_value_list(Es)). + list_to_tuple(literal_value_list(Es)); +literal_value(#c_map{arg=Cm,es=Cmps}) -> + M = literal_value(Cm), + lists:foldl(fun(#c_map_pair{ key=Ck, val=Cv },Mi) -> + K = literal_value(Ck), + V = literal_value(Cv), + maps:put(K,V,Mi) + end, M, Cmps). literal_value_list(Vals) -> [literal_value(V) || V <- Vals]. @@ -105,7 +112,7 @@ vu_expr(V, #c_cons{hd=H,tl=T}) -> vu_expr(V, H) orelse vu_expr(V, T); vu_expr(V, #c_tuple{es=Es}) -> vu_expr_list(V, Es); -vu_expr(V, #c_map{var=M,es=Es}) -> +vu_expr(V, #c_map{arg=M,es=Es}) -> vu_expr(V, M) orelse vu_expr_list(V, Es); vu_expr(V, #c_map_pair{key=Key,val=Val}) -> vu_expr_list(V, [Key,Val]); @@ -205,6 +212,8 @@ vu_pattern(V, #c_tuple{es=Es}, St) -> vu_pattern_list(V, Es, St); vu_pattern(V, #c_binary{segments=Ss}, St) -> vu_pat_seg_list(V, Ss, St); +vu_pattern(V, #c_map{es=Es}, St) -> + vu_map_pairs(V, Es, St); vu_pattern(V, #c_alias{var=Var,pat=P}, St0) -> case vu_pattern(V, Var, St0) of {true,_}=St1 -> St1; @@ -227,6 +236,13 @@ vu_pat_seg_list(V, Ss, St) -> end end, St, Ss). +vu_map_pairs(V, [#c_map_pair{val=Pat}|T], St0) -> + case vu_pattern(V, Pat, St0) of + {true,_}=St -> St; + St -> vu_map_pairs(V, T, St) + end; +vu_map_pairs(_, [], St) -> St. + -spec vu_var_list(cerl:var_name(), [cerl:c_var()]) -> boolean(). vu_var_list(V, Vs) -> diff --git a/lib/compiler/src/core_parse.hrl b/lib/compiler/src/core_parse.hrl index d54715ef59..4a00535360 100644 --- a/lib/compiler/src/core_parse.hrl +++ b/lib/compiler/src/core_parse.hrl @@ -34,7 +34,7 @@ -record(c_apply, {anno=[], op, % op :: Tree, args}). % args :: [Tree] --record(c_binary, {anno=[], segments}). % segments :: [#c_bitstr{}] +-record(c_binary, {anno=[], segments :: [cerl:c_bitstr()]}). -record(c_bitstr, {anno=[], val, % val :: Tree, size, % size :: Tree, @@ -70,6 +70,15 @@ -record(c_literal, {anno=[], val}). % val :: literal() +-record(c_map, {anno=[], + arg=#c_literal{val=#{}} :: cerl:c_var() | cerl:c_literal(), + es :: [cerl:c_map_pair()]}). + +-record(c_map_pair, {anno=[], + op :: #c_literal{val::'assoc'} | #c_literal{val::'exact'}, + key, + val}). + -record(c_module, {anno=[], name, % name :: Tree, exports, % exports :: [Tree], attrs, % attrs :: [#c_def{}], @@ -96,12 +105,3 @@ -record(c_values, {anno=[], es}). % es :: [Tree] -record(c_var, {anno=[], name :: cerl:var_name()}). - --record(c_map_pair, {anno=[], - op :: #c_literal{val::'assoc'} | #c_literal{val::'exact'}, - key, - val}). - --record(c_map, {anno=[], - var=#c_literal{val=[]} :: #c_var{} | #c_literal{}, - es :: [#c_map_pair{}]}). diff --git a/lib/compiler/src/core_parse.yrl b/lib/compiler/src/core_parse.yrl index b8db0f683a..a66ad4235f 100644 --- a/lib/compiler/src/core_parse.yrl +++ b/lib/compiler/src/core_parse.yrl @@ -21,7 +21,7 @@ %% Have explicit productions for annotated phrases named anno_XXX. %% This just does an XXX and adds the annotation. -Expect 1. +Expect 0. Nonterminals @@ -285,9 +285,9 @@ tuple -> '{' '}' : c_tuple([]). tuple -> '{' anno_expressions '}' : c_tuple('$2'). map_expr -> '~' '{' '}' '~' : #c_map{es=[]}. -map_expr -> '~' '{' map_pairs '}' '~' : #c_map{es='$3'}. -map_expr -> variable '~' '{' '}' '~' : #c_map{var='$1',es=[]}. -map_expr -> variable '~' '{' map_pairs '}' '~' : #c_map{var='$1',es='$4'}. +map_expr -> '~' '{' map_pairs '}' '~' : #c_map{es='$3'}. +map_expr -> '~' '{' map_pairs '|' variable '}' '~' : #c_map{arg='$5',es='$3'}. +map_expr -> '~' '{' map_pairs '|' map_expr '}' '~' : #c_map{arg='$5',es='$3'}. map_pairs -> map_pair : ['$1']. map_pairs -> map_pair ',' map_pairs : ['$1' | '$3']. diff --git a/lib/compiler/src/core_pp.erl b/lib/compiler/src/core_pp.erl index faa26ec6df..83412ecdd7 100644 --- a/lib/compiler/src/core_pp.erl +++ b/lib/compiler/src/core_pp.erl @@ -118,6 +118,16 @@ format_1(#c_literal{val=Tuple}, Ctxt) when is_tuple(Tuple) -> format_1(#c_literal{anno=A,val=Bitstring}, Ctxt) when is_bitstring(Bitstring) -> Segs = segs_from_bitstring(Bitstring), format_1(#c_binary{anno=A,segments=Segs}, Ctxt); +format_1(#c_literal{anno=A,val=M},Ctxt) when is_map(M) -> + Pairs = maps:to_list(M), + Op = case Ctxt of + #ctxt{ class = clause } -> exact; + _ -> assoc + end, + Cpairs = [#c_map_pair{op=#c_literal{val=Op}, + key=#c_literal{val=V}, + val=#c_literal{val=K}} || {K,V} <- Pairs], + format_1(#c_map{anno=A,arg=#c_literal{val=#{}},es=Cpairs},Ctxt); format_1(#c_var{name={I,A}}, _) -> [core_atom(I),$/,integer_to_list(A)]; format_1(#c_var{name=V}, _) -> @@ -161,15 +171,15 @@ format_1(#c_tuple{es=Es}, Ctxt) -> format_hseq(Es, ",", add_indent(Ctxt, 1), fun format/2), $} ]; -format_1(#c_map{var=#c_var{}=Var,es=Es}, Ctxt) -> - [format_1(Var, Ctxt), - "~{", +format_1(#c_map{arg=#c_literal{val=M},es=Es}, Ctxt) when is_map(M),map_size(M)=:=0 -> + ["~{", format_hseq(Es, ",", add_indent(Ctxt, 1), fun format/2), "}~" ]; -format_1(#c_map{es=Es}, Ctxt) -> +format_1(#c_map{arg=Var,es=Es}, Ctxt) -> ["~{", format_hseq(Es, ",", add_indent(Ctxt, 1), fun format/2), + "|",format(Var, add_indent(Ctxt, 1)), "}~" ]; format_1(#c_map_pair{op=#c_literal{val=assoc},key=K,val=V}, Ctxt) -> diff --git a/lib/compiler/src/erl_bifs.erl b/lib/compiler/src/erl_bifs.erl index 3ad3c8c690..6c75538194 100644 --- a/lib/compiler/src/erl_bifs.erl +++ b/lib/compiler/src/erl_bifs.erl @@ -91,6 +91,7 @@ is_pure(erlang, is_float, 1) -> true; is_pure(erlang, is_function, 1) -> true; is_pure(erlang, is_integer, 1) -> true; is_pure(erlang, is_list, 1) -> true; +is_pure(erlang, is_map, 1) -> true; is_pure(erlang, is_number, 1) -> true; is_pure(erlang, is_pid, 1) -> true; is_pure(erlang, is_port, 1) -> true; diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index 6fdeea51d1..ed8f609082 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -72,7 +72,7 @@ -import(lists, [map/2,foldl/3,foldr/3,mapfoldl/3,all/2,any/2, reverse/1,reverse/2,member/2,nth/2,flatten/1,unzip/1]). --import(cerl, [ann_c_cons/3,ann_c_tuple/2]). +-import(cerl, [ann_c_cons/3,ann_c_map/3,ann_c_tuple/2]). -include("core_parse.hrl"). @@ -246,7 +246,7 @@ expr(#c_tuple{anno=Anno,es=Es0}=Tuple, Ctxt, Sub) -> value -> ann_c_tuple(Anno, Es) end; -expr(#c_map{var=V0,es=Es0}=Map, Ctxt, Sub) -> +expr(#c_map{anno=Anno,arg=V0,es=Es0}=Map, Ctxt, Sub) -> Es = pair_list(Es0, Ctxt, Sub), case Ctxt of effect -> @@ -254,7 +254,7 @@ expr(#c_map{var=V0,es=Es0}=Map, Ctxt, Sub) -> expr(make_effect_seq(Es, Sub), Ctxt, Sub); value -> V = expr(V0, Ctxt, Sub), - Map#c_map{var=V,es=Es} + ann_c_map(Anno,V,Es) end; expr(#c_binary{segments=Ss}=Bin0, Ctxt, Sub) -> %% Warn for useless building, but always build the binary @@ -1229,6 +1229,11 @@ is_non_numeric([H|T]) -> is_non_numeric(H) andalso is_non_numeric(T); is_non_numeric(Tuple) when is_tuple(Tuple) -> is_non_numeric_tuple(Tuple, tuple_size(Tuple)); +is_non_numeric(Map) when is_map(Map) -> + %% Note that 17.x and 18.x compare keys in different ways. + %% Be very conservative -- require that both keys and values + %% are non-numeric. + is_non_numeric(maps:to_list(Map)); is_non_numeric(Num) when is_number(Num) -> false; is_non_numeric(_) -> true. @@ -1338,9 +1343,12 @@ eval_element(Call, #c_literal{val=Pos}, #c_var{name=V}, Types) {ok,#c_tuple{es=Elements}} -> if 1 =< Pos, Pos =< length(Elements) -> - case lists:nth(Pos, Elements) of - #c_alias{var=Alias} -> Alias; - Res -> Res + El = lists:nth(Pos, Elements), + try + pat_to_expr(El) + catch + throw:impossible -> + Call end; true -> eval_failure(Call, badarg) @@ -1378,6 +1386,7 @@ eval_is_record(Call, _, _, _, _) -> Call. is_not_integer(#c_literal{val=Val}) when not is_integer(Val) -> true; is_not_integer(#c_tuple{}) -> true; is_not_integer(#c_cons{}) -> true; +is_not_integer(#c_map{}) -> true; is_not_integer(_) -> false. %% is_not_tuple(Core) -> true | false. @@ -1385,6 +1394,7 @@ is_not_integer(_) -> false. is_not_tuple(#c_literal{val=Val}) when not is_tuple(Val) -> true; is_not_tuple(#c_cons{}) -> true; +is_not_tuple(#c_map{}) -> true; is_not_tuple(_) -> false. %% eval_setelement(Call, Pos, Tuple, NewVal) -> Core. @@ -1554,16 +1564,8 @@ map_pair_pattern_list(Ps0, Isub, Osub0) -> {Ps,Osub}. 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), + K = expr(K0, Isub), + {V,Osub} = pattern(V0,Isub,Osub0), {Pair#c_map_pair{key=K,val=V},{Isub,Osub}}. bin_pattern_list(Ps0, Isub, Osub0) -> @@ -1810,9 +1812,14 @@ opt_bool_clauses([#c_clause{pats=[#c_literal{val=Lit}], true -> %% This clause will match. C = C0#c_clause{body=opt_bool_case(B)}, - case Lit of - false -> [C|opt_bool_clauses(Cs, SeenT, true)]; - true -> [C|opt_bool_clauses(Cs, true, SeenF)] + case {Lit,SeenT,SeenF} of + {false,_,false} -> + [C|opt_bool_clauses(Cs, SeenT, true)]; + {true,false,_} -> + [C|opt_bool_clauses(Cs, true, SeenF)]; + _ -> + add_warning(C, nomatch_shadow), + opt_bool_clauses(Cs, SeenT, SeenF) end end; opt_bool_clauses([#c_clause{pats=Ps,guard=#c_literal{val=true}}=C|Cs], SeenT, SeenF) -> @@ -2024,9 +2031,9 @@ case_opt(Arg, Cs0, Sub) -> case_opt_args([A0|As0], Cs0, Sub, LitExpr, Acc) -> case case_opt_arg(A0, Sub, Cs0, LitExpr) of - error -> + {error,Cs1} -> %% Nothing to be done. Move on to the next argument. - Cs = [{Ps,C,[P|PsAcc],Bs} || {[P|Ps],C,PsAcc,Bs} <- Cs0], + Cs = [{Ps,C,[P|PsAcc],Bs} || {[P|Ps],C,PsAcc,Bs} <- Cs1], case_opt_args(As0, Cs, Sub, LitExpr, [A0|Acc]); {ok,As1,Cs} -> %% The argument was either expanded (from tuple/list) or @@ -2041,17 +2048,18 @@ case_opt_args([], Cs, _Sub, _LitExpr, Acc) -> %% Try to expand one argument to several arguments (if tuple/list) %% or to remove a literal argument. %% -case_opt_arg(E0, Sub, Cs, LitExpr) -> +case_opt_arg(E0, Sub, Cs0, LitExpr) -> E = maybe_replace_var(E0, Sub), case cerl:is_data(E) of false -> - error; + {error,Cs0}; true -> + Cs = case_opt_nomatch(E, Cs0, LitExpr), case cerl:data_type(E) of {atomic,_} -> - case_opt_lit(E, Cs, LitExpr); + case_opt_lit(E, Cs); _ -> - case_opt_data(E, Cs, LitExpr) + case_opt_data(E, Cs) end end. @@ -2073,19 +2081,67 @@ maybe_replace_var_1(E, #sub{t=Tdb}) -> false -> E; true -> - cerl_trees:map(fun(C) -> - case cerl:is_c_alias(C) of - false -> C; - true -> cerl:alias_pat(C) - end - end, T0) + %% The pattern was a tuple. Now we must make sure + %% that the elements of the tuple are suitable. In + %% particular, we don't want binary or map + %% construction here, since that means that the + %% binary or map will be constructed in the 'case' + %% argument. That is wasteful for binaries. Even + %% worse is that any map pattern that use the ':=' + %% operator will fail when used in map + %% construction (only the '=>' operator is allowed + %% when constructing a map from scratch). + ToData = fun coerce_to_data/1, + try + cerl_trees:map(ToData, T0) + catch + throw:impossible -> + %% Something unsuitable was found (map or + %% or binary). Keep the variable. + E + end end; error -> E end. -%% case_opt_lit(Literal, Clauses0, LitExpr) -> -%% {ok,[],Clauses} | error +%% coerce_to_data(Core) -> Core' +%% Coerce an element originally from a pattern to an data item or or +%% variable. Throw an 'impossible' exception if non-data Core Erlang +%% terms such as binary construction or map construction are +%% encountered. + +coerce_to_data(C) -> + case cerl:is_c_alias(C) of + false -> + case cerl:is_data(C) orelse cerl:is_c_var(C) of + true -> C; + false -> throw(impossible) + end; + true -> + coerce_to_data(cerl:alias_pat(C)) + end. + +%% case_opt_nomatch(E, Clauses, LitExpr) -> Clauses' +%% Remove all clauses that cannot possibly match. + +case_opt_nomatch(E, [{[P|_],C,_,_}=Current|Cs], LitExpr) -> + case cerl_clauses:match(P, E) of + none -> + %% The pattern will not match the case expression. Remove + %% the clause. Unless the entire case expression is a + %% literal, also emit a warning. + case LitExpr of + false -> add_warning(C, nomatch_clause_type); + true -> ok + end, + case_opt_nomatch(E, Cs, LitExpr); + _ -> + [Current|case_opt_nomatch(E, Cs, LitExpr)] + end; +case_opt_nomatch(_, [], _) -> []. + +%% case_opt_lit(Literal, Clauses0) -> {ok,[],Clauses} | error %% The current part of the case expression is a literal. That %% means that we will know at compile-time whether a clause %% will match, and we can remove the corresponding pattern from @@ -2094,59 +2150,48 @@ maybe_replace_var_1(E, #sub{t=Tdb}) -> %% The only complication is if the literal is a binary. Binary %% pattern matching is tricky, so we will give up in that case. -case_opt_lit(Lit, Cs0, LitExpr) -> - try case_opt_lit_1(Cs0, Lit, LitExpr) of +case_opt_lit(Lit, Cs0) -> + try case_opt_lit_1(Lit, Cs0) of Cs -> {ok,[],Cs} catch throw:impossible -> - error + {error,Cs0} end. -case_opt_lit_1([{[P|Ps],C,PsAcc,Bs0}|Cs], E, LitExpr) -> +case_opt_lit_1(E, [{[P|Ps],C,PsAcc,Bs0}|Cs]) -> + %% Non-matching clauses have already been removed + %% in case_opt_nomatch/3. case cerl_clauses:match(P, E) of - none -> - %% The pattern will not match the literal. Remove the clause. - %% Unless the entire case expression is a literal, also - %% emit a warning. - case LitExpr of - false -> add_warning(C, nomatch_clause_type); - true -> ok - end, - case_opt_lit_1(Cs, E, LitExpr); {true,Bs} -> %% The pattern matches the literal. Remove the pattern %% and update the bindings. - [{Ps,C,PsAcc,Bs++Bs0}|case_opt_lit_1(Cs, E, LitExpr)]; + [{Ps,C,PsAcc,Bs++Bs0}|case_opt_lit_1(E, Cs)]; {false,_} -> %% Binary literal and pattern. We are not sure whether %% the pattern will match. throw(impossible) end; -case_opt_lit_1([], _, _) -> []. +case_opt_lit_1(_, []) -> []. %% case_opt_data(Expr, Clauses0, LitExpr) -> {ok,Exprs,Clauses} -case_opt_data(E, Cs0, LitExpr) -> +case_opt_data(E, Cs0) -> Es = cerl:data_es(E), - Cs = case_opt_data_1(Cs0, Es, - {cerl:data_type(E),cerl:data_arity(E)}, - LitExpr), - {ok,Es,Cs}. - -case_opt_data_1([{[P|Ps0],C,PsAcc,Bs0}|Cs], Es, TypeSig, LitExpr) -> - case case_data_pat(P, TypeSig) of - {ok,Ps1,Bs1} -> - [{Ps1++Ps0,C,PsAcc,Bs1++Bs0}| - case_opt_data_1(Cs, Es, TypeSig,LitExpr)]; - error -> - case LitExpr of - false -> add_warning(C, nomatch_clause_type); - true -> ok - end, - case_opt_data_1(Cs, Es, TypeSig, LitExpr) - end; -case_opt_data_1([], _, _, _) -> []. + TypeSig = {cerl:data_type(E),cerl:data_arity(E)}, + try case_opt_data_1(Cs0, Es, TypeSig) of + Cs -> + {ok,Es,Cs} + catch + throw:impossible -> + {error,Cs0} + end. + +case_opt_data_1([{[P|Ps0],C,PsAcc,Bs0}|Cs], Es, TypeSig) -> + {ok,Ps1,Bs1} = case_data_pat(P, TypeSig), + [{Ps1++Ps0,C,PsAcc,Bs1++Bs0}| + case_opt_data_1(Cs, Es, TypeSig)]; +case_opt_data_1([], _, _) -> []. %% case_data_pat(Pattern, Type, Arity) -> {ok,[Pattern],[{AliasVar,Pat}]} | error. @@ -2155,12 +2200,7 @@ case_data_pat(P, TypeSig) -> false -> case_data_pat_var(P, TypeSig); true -> - case {cerl:data_type(P),cerl:data_arity(P)} of - TypeSig -> - {ok,cerl:data_es(P),[]}; - {_,_} -> - error - end + {ok,cerl:data_es(P),[]} end. %% case_data_pat_var(Pattern, {DataType,ArityType}) -> @@ -2180,35 +2220,38 @@ case_data_pat_var(P, {Type,Arity}=TypeSig) -> alias -> V = cerl:alias_var(P), Apat = cerl:alias_pat(P), - case case_data_pat(Apat, TypeSig) of - {ok,Ps,Bs} -> - {ok,Ps,[{V,cerl:ann_make_data(Ann, Type, unalias_pat_list(Ps))}|Bs]}; - error -> - error - end; - _ -> - error + {ok,Ps,Bs} = case_data_pat(Apat, TypeSig), + {ok,Ps,[{V,cerl:ann_make_data(Ann, Type, + pat_to_expr_list(Ps))}|Bs]} end. -%% unalias_pat(Pattern) -> Pattern. -%% Remove all the aliases in a pattern but using the alias variables -%% instead of the values. We KNOW they will be bound. +%% pat_to_expr(Pattern) -> Expression. +%% Convert a pattern to an expression if possible. We KNOW that +%% all variables in the pattern will be bound. +%% +%% Throw an 'impossible' exception if a map or (non-literal) +%% binary is encountered. Trying to use a map pattern as an +%% expression is incorrect, while rebuilding a potentially +%% huge binary in an expression would be wasteful. -unalias_pat(P) -> - case cerl:is_c_alias(P) of - true -> +pat_to_expr(P) -> + case cerl:type(P) of + alias -> cerl:alias_var(P); - false -> + var -> + P; + _ -> case cerl:is_data(P) of false -> - P; + %% Map or binary. + throw(impossible); true -> - Es = unalias_pat_list(cerl:data_es(P)), + Es = pat_to_expr_list(cerl:data_es(P)), cerl:update_data(P, cerl:data_type(P), Es) end end. -unalias_pat_list(Ps) -> [unalias_pat(P) || P <- Ps]. +pat_to_expr_list(Ps) -> [pat_to_expr(P) || P <- Ps]. make_vars(A, Max) -> make_vars(A, 1, Max). @@ -2240,23 +2283,23 @@ letify(#c_var{name=Vname}=Var, Val, Body) -> %% opt_case_in_let(LetExpr) -> LetExpr' -opt_case_in_let(#c_let{vars=Vs,arg=Arg,body=B}=Let) -> - opt_case_in_let_0(Vs, Arg, B, Let). +opt_case_in_let(#c_let{vars=Vs,arg=Arg,body=B}=Let, Sub) -> + opt_case_in_let_0(Vs, Arg, B, Let, Sub). opt_case_in_let_0([#c_var{name=V}], Arg, - #c_case{arg=#c_var{name=V},clauses=Cs}=Case, Let) -> + #c_case{arg=#c_var{name=V},clauses=Cs}=Case, Let, Sub) -> case opt_case_in_let_1(V, Arg, Cs) of impossible -> case is_simple_case_arg(Arg) andalso not core_lib:is_var_used(V, Case#c_case{arg=#c_literal{val=nil}}) of true -> - expr(opt_bool_case(Case#c_case{arg=Arg,clauses=Cs}), sub_new()); + expr(opt_bool_case(Case#c_case{arg=Arg,clauses=Cs}), sub_new(Sub)); false -> Let end; Expr -> Expr end; -opt_case_in_let_0(_, _, _, Let) -> Let. +opt_case_in_let_0(_, _, _, Let, _) -> Let. opt_case_in_let_1(V, Arg, Cs) -> try @@ -2599,7 +2642,7 @@ opt_simple_let_2(Let0, Vs0, Arg0, Body0, effect, Sub) -> expr(#c_seq{arg=Arg,body=Body}, effect, sub_new_preserve_types(Sub)); true -> Let = Let0#c_let{vars=Vs,arg=Arg,body=Body}, - opt_case_in_let_arg(opt_case_in_let(Let), effect, Sub) + opt_case_in_let_arg(opt_case_in_let(Let, Sub), effect, Sub) end end; opt_simple_let_2(Let, Vs0, Arg0, Body, value, Sub) -> @@ -2622,7 +2665,7 @@ opt_simple_let_2(Let, Vs0, Arg0, Body, value, Sub) -> expr(#c_seq{arg=Arg,body=Body}, value, sub_new_preserve_types(Sub)); {Vs,Arg,Body} -> opt_case_in_let_arg( - opt_case_in_let(Let#c_let{vars=Vs,arg=Arg,body=Body}), + opt_case_in_let(Let#c_let{vars=Vs,arg=Arg,body=Body}, Sub), value, Sub) end. diff --git a/lib/compiler/src/sys_pre_expand.erl b/lib/compiler/src/sys_pre_expand.erl index 91a46a20fe..761ae8409c 100644 --- a/lib/compiler/src/sys_pre_expand.erl +++ b/lib/compiler/src/sys_pre_expand.erl @@ -232,7 +232,7 @@ pattern({map,Line,Ps}, St0) -> {TPs,St1} = pattern_list(Ps, St0), {{map,Line,TPs},St1}; pattern({map_field_exact,Line,K0,V0}, St0) -> - {K,St1} = pattern(K0, St0), + {K,St1} = expr(K0, St0), {V,St2} = pattern(V0, St1), {{map_field_exact,Line,K,V},St2}; %%pattern({struct,Line,Tag,Ps}, St0) -> diff --git a/lib/compiler/src/v3_codegen.erl b/lib/compiler/src/v3_codegen.erl index f1331d1fe7..47a357c23d 100644 --- a/lib/compiler/src/v3_codegen.erl +++ b/lib/compiler/src/v3_codegen.erl @@ -960,7 +960,6 @@ select_extract_map(Src, Vs, Fail, I, Vdb, Bef, St) -> end, {{[],[]},Bef}, Vs), Code = case {HasKs,GetVs} of - {[],[]} -> {[],Aft,St}; {HasKs,[]} -> [{test,has_map_fields,{f,Fail},Rsrc,{list,HasKs}}]; {[],GetVs} -> @@ -1553,8 +1552,7 @@ map_pair_strip_and_termsort(Es) -> Ls = [{K,V}||{_,K,V}<-Es], lists:sort(fun ({{_,A},_}, {{_,B},_}) -> erts_internal:cmp_term(A,B) =< 0; ({nil,_}, {{_,B},_}) -> [] =< B; - ({{_,A},_}, {nil,_}) -> A =< []; - ({nil,_}, {nil,_}) -> true + ({{_,A},_}, {nil,_}) -> A =< [] end, Ls). %%% diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index 3d17557e01..59ec0d4199 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2013. All Rights Reserved. +%% Copyright Ericsson AB 1999-2014. 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 @@ -77,10 +77,13 @@ splitwith/2,keyfind/3,sort/1,foreach/2,droplast/1,last/1]). -import(ordsets, [add_element/2,del_element/2,is_element/2, union/1,union/2,intersection/2,subtract/2]). --import(cerl, [ann_c_cons/3,ann_c_cons_skel/3,ann_c_tuple/2,c_tuple/1]). +-import(cerl, [ann_c_cons/3,ann_c_cons_skel/3,ann_c_tuple/2,c_tuple/1, + ann_c_map/2, ann_c_map/3]). -include("core_parse.hrl"). +-define(REC_OFFSET, 100000000). % Also in erl_expand_records. + %% Internal core expressions and help functions. %% N.B. annotations fields in place as normal Core expressions. @@ -500,7 +503,7 @@ expr({cons,L,H0,T0}, St0) -> {H1,Hps,St1} = safe(H0, St0), {T1,Tps,St2} = safe(T0, St1), A = lineno_anno(L, St2), - {ann_c_cons(A, H1, T1),Hps ++ Tps,St2}; + {annotate_cons(A, H1, T1, St2),Hps ++ Tps,St2}; expr({lc,L,E,Qs0}, St0) -> {Qs1,St1} = preprocess_quals(L, Qs0, St0), lc_tq(L, E, Qs1, #c_literal{anno=lineno_anno(L, St1),val=[]}, St1); @@ -508,19 +511,38 @@ expr({bc,L,E,Qs}, St) -> bc_tq(L, E, Qs, {nil,L}, St); expr({tuple,L,Es0}, St0) -> {Es1,Eps,St1} = safe_list(Es0, St0), - A = lineno_anno(L, St1), - {ann_c_tuple(A, Es1),Eps,St1}; + A = record_anno(L, St1), + {annotate_tuple(A, Es1, St1),Eps,St1}; expr({map,L,Es0}, St0) -> % erl_lint should make sure only #{ K => V } are allowed % in map construction. - {Es1,Eps,St1} = map_pair_list(Es0, St0), - A = lineno_anno(L, St1), - {#c_map{anno=A,es=Es1},Eps,St1}; + try map_pair_list(Es0, St0) of + {Es1,Eps,St1} -> + A = lineno_anno(L, St1), + {ann_c_map(A,Es1),Eps,St1} + catch + throw:{bad_map,Warning} -> + St = add_warning(L, Warning, St0), + LineAnno = lineno_anno(L, St), + As = [#c_literal{anno=LineAnno,val=badarg}], + {#icall{anno=#a{anno=LineAnno}, %Must have an #a{} + module=#c_literal{anno=LineAnno,val=erlang}, + name=#c_literal{anno=LineAnno,val=error}, + args=As},[],St} + end; expr({map,L,M0,Es0}, St0) -> - {M1,Mps,St1} = safe(M0, St0), - {Es1,Eps,St2} = map_pair_list(Es0, St1), - A = lineno_anno(L, St2), - {#c_map{anno=A,var=M1,es=Es1},Mps++Eps,St2}; + try expr_map(M0,Es0,lineno_anno(L, St0),St0) of + {_,_,_}=Res -> Res + catch + throw:{bad_map,Warning} -> + St = add_warning(L, Warning, St0), + LineAnno = lineno_anno(L, St), + As = [#c_literal{anno=LineAnno,val=badarg}], + {#icall{anno=#a{anno=LineAnno}, %Must have an #a{} + module=#c_literal{anno=LineAnno,val=erlang}, + name=#c_literal{anno=LineAnno,val=error}, + args=As},[],St} + end; expr({bin,L,Es0}, St0) -> try expr_bin(Es0, lineno_anno(L, St0), St0) of {_,_,_}=Res -> Res @@ -625,8 +647,8 @@ expr({'fun',L,{clauses,Cs},Id}, St) -> fun_tq(Id, Cs, L, St, unnamed); expr({named_fun,L,'_',Cs,Id}, St) -> fun_tq(Id, Cs, L, St, unnamed); -expr({named_fun,L,Name,Cs,{Index,Uniq,_Fname}}, St) -> - fun_tq({Index,Uniq,Name}, Cs, L, St, {named, Name}); +expr({named_fun,L,Name,Cs,Id}, St) -> + fun_tq(Id, Cs, L, St, {named,Name}); expr({call,L,{remote,_,M,F},As0}, #core{wanted=Wanted}=St0) -> {[M1,F1|As1],Aps,St1} = safe_list([M,F|As0], St0), Lanno = lineno_anno(L, St1), @@ -730,22 +752,77 @@ make_bool_switch_guard(L, E, V, T, F) -> {clause,NegL,[V],[],[V]} ]}. +expr_map(M0,Es0,A,St0) -> + {M1,Mps,St1} = safe(M0, St0), + case is_valid_map_src(M1) of + true -> + case {M1,Es0} of + {#c_var{}, []} -> + %% transform M#{} to is_map(M) + {Vpat,St2} = new_var(St1), + {Fpat,St3} = new_var(St2), + Cs = [#iclause{ + anno=A, + pats=[Vpat], + guard=[#icall{anno=#a{anno=A}, + module=#c_literal{anno=A,val=erlang}, + name=#c_literal{anno=A,val=is_map}, + args=[Vpat]}], + body=[Vpat]}], + Fc = fail_clause([Fpat], A, #c_literal{val=badarg}), + {#icase{anno=#a{anno=A},args=[M1],clauses=Cs,fc=Fc},Mps,St3}; + {_,_} -> + {Es1,Eps,St2} = map_pair_list(Es0, St1), + {ann_c_map(A,M1,Es1),Mps++Eps,St2} + end; + false -> throw({bad_map,bad_map}) + end. + +is_valid_map_src(#c_literal{val = M}) when is_map(M) -> true; +is_valid_map_src(#c_map{}) -> true; +is_valid_map_src(#c_var{}) -> true; +is_valid_map_src(_) -> false. + map_pair_list(Es, St) -> foldr(fun ({map_field_assoc,L,K0,V0}, {Ces,Esp,St0}) -> - {K,Ep0,St1} = safe(K0, St0), + {K1,Ep0,St1} = safe(K0, St0), + K = ensure_valid_map_key(K1), {V,Ep1,St2} = safe(V0, St1), A = lineno_anno(L, St2), Pair = #c_map_pair{op=#c_literal{val=assoc},anno=A,key=K,val=V}, {[Pair|Ces],Ep0 ++ Ep1 ++ Esp,St2}; ({map_field_exact,L,K0,V0}, {Ces,Esp,St0}) -> - {K,Ep0,St1} = safe(K0, St0), + {K1,Ep0,St1} = safe(K0, St0), + K = ensure_valid_map_key(K1), {V,Ep1,St2} = safe(V0, St1), A = lineno_anno(L, St2), Pair = #c_map_pair{op=#c_literal{val=exact},anno=A,key=K,val=V}, {[Pair|Ces],Ep0 ++ Ep1 ++ Esp,St2} end, {[],[],St}, Es). +ensure_valid_map_key(K0) -> + case coalesced_map_key(K0) of + {ok,K1} -> K1; + error -> throw({bad_map,bad_map_key}) + end. + +coalesced_map_key(#c_literal{}=K) -> {ok,K}; +%% Dialyzer hack redux +%% DO coalesce tuples and list in maps for dialyzer +%% Dialyzer tries to break this apart, don't let it +coalesced_map_key(#c_tuple{}=K) -> + case core_lib:is_literal(K) of + true -> {ok,cerl:fold_literal(K)}; + false -> error + end; +coalesced_map_key(#c_cons{}=K) -> + case core_lib:is_literal(K) of + true -> {ok,cerl:fold_literal(K)}; + false -> error + end; +coalesced_map_key(_) -> error. + %% try_exception([ExcpClause], St) -> {[ExcpVar],Handler,St}. try_exception(Ecs0, St0) -> @@ -1501,9 +1578,9 @@ pattern({atom,L,A}, St) -> #c_literal{anno=lineno_anno(L, St),val=A}; pattern({string,L,S}, St) -> #c_literal{anno=lineno_anno(L, St),val=S}; pattern({nil,L}, St) -> #c_literal{anno=lineno_anno(L, St),val=[]}; pattern({cons,L,H,T}, St) -> - ann_c_cons(lineno_anno(L, St), pattern(H, St), pattern(T, St)); + annotate_cons(lineno_anno(L, St), pattern(H, St), pattern(T, St), St); pattern({tuple,L,Ps}, St) -> - ann_c_tuple(lineno_anno(L, St), pattern_list(Ps, St)); + annotate_tuple(record_anno(L, St), pattern_list(Ps, St), St); pattern({map,L,Ps}, St) -> #c_map{anno=lineno_anno(L, St), es=pattern_map_pairs(Ps, St)}; pattern({bin,L,Ps}, St) -> @@ -1548,25 +1625,24 @@ pattern_alias_map_pair_patterns([Cv]) -> Cv; pattern_alias_map_pair_patterns([Cv1,Cv2|Cvs]) -> pattern_alias_map_pair_patterns([pat_alias(Cv1,Cv2)|Cvs]). -pattern_map_pair({map_field_exact,L,K,V}, St) -> - %% FIXME: Better way to construct literals? or missing case - %% {Key,_,_} = expr(K, St), - Key = case K of - {bin,L,Es0} -> - case constant_bin(Es0) of - error -> - throw(badmatch); - Bin -> - #c_literal{anno=lineno_anno(L,St),val=Bin} - end; - _ -> - pattern(K,St) - end, +pattern_map_pair({map_field_exact,L,K,V},St) -> #c_map_pair{anno=lineno_anno(L, St), - op=#c_literal{val=exact}, - key=Key, + op=#c_literal{val=exact}, + key=pattern_map_key(K,St), val=pattern(V, St)}. +pattern_map_key(K,St) -> + %% Throws 'nomatch' if the key can't be a literal + %% this will be a cryptic error message but it is better than nothing + case expr(K,St) of + {Key0,[],_} -> + %% Dialyzer hack redux + case coalesced_map_key(Key0) of + {ok,Key1} -> Key1; + error -> throw(nomatch) + end; + _ -> throw(nomatch) + end. %% pat_bin([BinElement], State) -> [BinSeg]. @@ -1672,6 +1748,26 @@ fail_clause(Pats, Anno, Arg) -> body=[#iprimop{anno=#a{anno=Anno},name=#c_literal{val=match_fail}, args=[Arg]}]}. +annotate_tuple(A, Es, St) -> + case member(dialyzer, St#core.opts) of + true -> + %% Do not coalesce constant tuple elements. A Hack. + Node = cerl:ann_c_tuple(A, [cerl:c_var(any)]), + cerl:update_c_tuple_skel(Node, Es); + false -> + ann_c_tuple(A, Es) + end. + +annotate_cons(A, H, T, St) -> + case member(dialyzer, St#core.opts) of + true -> + %% Do not coalesce constant conses. A Hack. + Node= cerl:ann_c_cons(A, cerl:c_var(any), cerl:c_var(any)), + cerl:update_c_cons_skel(Node, H, T); + false -> + ann_c_cons(A, H, T) + end. + ubody(B, St) -> uexpr(B, [], St). %% uclauses([Lclause], [KnownVar], State) -> {[Lclause],State}. @@ -2166,6 +2262,8 @@ lit_vars(Lit) -> lit_vars(Lit, []). lit_vars(#c_cons{hd=H,tl=T}, Vs) -> lit_vars(H, lit_vars(T, Vs)); lit_vars(#c_tuple{es=Es}, Vs) -> lit_list_vars(Es, Vs); +lit_vars(#c_map{arg=V,es=Es}, Vs) -> lit_vars(V, lit_list_vars(Es, Vs)); +lit_vars(#c_map_pair{key=K,val=V}, Vs) -> lit_vars(K, lit_vars(V, Vs)); lit_vars(#c_var{name=V}, Vs) -> add_element(V, Vs); lit_vars(_, Vs) -> Vs. %These are atomic @@ -2187,6 +2285,23 @@ bitstr_vars(Segs, Vs) -> lit_vars(V, lit_vars(S, Vs0)) end, Vs, Segs). +record_anno(L, St) when L >= ?REC_OFFSET -> + case member(dialyzer, St#core.opts) of + true -> + [record | lineno_anno(L - ?REC_OFFSET, St)]; + false -> + lineno_anno(L, St) + end; +record_anno(L, St) when L < -?REC_OFFSET -> + case member(dialyzer, St#core.opts) of + true -> + [record | lineno_anno(L + ?REC_OFFSET, St)]; + false -> + lineno_anno(L, St) + end; +record_anno(L, St) -> + lineno_anno(L, St). + lineno_anno(L, St) -> {line, Line} = erl_parse:get_attribute(L, line), if @@ -2256,7 +2371,11 @@ is_simple_list(Es) -> lists:all(fun is_simple/1, Es). format_error(nomatch) -> "pattern cannot possibly match"; format_error(bad_binary) -> - "binary construction will fail because of a type mismatch". + "binary construction will fail because of a type mismatch"; +format_error(bad_map_key) -> + "map construction will fail because of none literal key (large binaries are not literals)"; +format_error(bad_map) -> + "map construction will fail because of a type mismatch". add_warning(Line, Term, #core{ws=Ws,file=[{file,File}]}=St) when Line >= 0 -> St#core{ws=[{File,[{location(Line),?MODULE,Term}]}|Ws]}; diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl index d00dd56f30..40d2f72b4c 100644 --- a/lib/compiler/src/v3_kernel.erl +++ b/lib/compiler/src/v3_kernel.erl @@ -272,9 +272,18 @@ expr(#c_cons{anno=A,hd=Ch,tl=Ct}, Sub, St0) -> expr(#c_tuple{anno=A,es=Ces}, Sub, St0) -> {Kes,Ep,St1} = atomic_list(Ces, Sub, St0), {#k_tuple{anno=A,es=Kes},Ep,St1}; -expr(#c_map{anno=A,var=Var0,es=Ces}, Sub, St0) -> - {Var,[],St1} = expr(Var0, Sub, St0), - map_split_pairs(A, Var, Ces, Sub, St1); +expr(#c_map{anno=A,arg=Var,es=Ces}, Sub, St0) -> + try expr_map(A,Var,Ces,Sub,St0) of + {_,_,_}=Res -> Res + catch + throw:bad_map -> + St1 = add_warning(get_line(A), bad_map, A, St0), + Erl = #c_literal{val=erlang}, + Name = #c_literal{val=error}, + Args = [#c_literal{val=badarg}], + Error = #c_call{anno=A,module=Erl,name=Name,args=Args}, + expr(Error, Sub, St1) + end; expr(#c_binary{anno=A,segments=Cv}, Sub, St0) -> try atomic_bin(Cv, Sub, St0) of {Kv,Ep,St1} -> @@ -496,6 +505,21 @@ translate_match_fail_1(Anno, As, Sub, #kern{ff=FF}) -> translate_fc(Args) -> [#c_literal{val=function_clause},make_list(Args)]. +expr_map(A,Var0,Ces,Sub,St0) -> + %% An extra pass of validation of Map src because of inlining + {Var,Mps,St1} = expr(Var0, Sub, St0), + case is_valid_map_src(Var) of + true -> + {Km,Eps,St2} = map_split_pairs(A, Var, Ces, Sub, St1), + {Km,Eps++Mps,St2}; + false -> throw(bad_map) + end. + +is_valid_map_src(#k_map{}) -> true; +is_valid_map_src(#k_literal{val=M}) when is_map(M) -> true; +is_valid_map_src(#k_var{}) -> true; +is_valid_map_src(_) -> false. + map_split_pairs(A, Var, Ces, Sub, St0) -> %% two steps %% 1. force variables @@ -557,8 +581,7 @@ map_key_clean(#k_literal{val=V}) -> {k_literal,V}; map_key_clean(#k_int{val=V}) -> {k_int,V}; map_key_clean(#k_float{val=V}) -> {k_float,V}; map_key_clean(#k_atom{val=V}) -> {k_atom,V}; -map_key_clean(#k_nil{}) -> k_nil; -map_key_clean(#k_var{name=V}) -> {k_var,V}. +map_key_clean(#k_nil{}) -> k_nil. %% call_type(Module, Function, Arity) -> call | bif | apply | error. @@ -1986,7 +2009,9 @@ format_error(nomatch_shadow) -> format_error(bad_call) -> "invalid module and/or function name; this call will always fail"; format_error(bad_segment_size) -> - "binary construction will fail because of a type mismatch". + "binary construction will fail because of a type mismatch"; +format_error(bad_map) -> + "map construction will fail because of a type mismatch". add_warning(none, Term, Anno, #kern{ws=Ws}=St) -> File = get_file(Anno), diff --git a/lib/compiler/src/v3_kernel_pp.erl b/lib/compiler/src/v3_kernel_pp.erl index b4e486f97c..b33eba50eb 100644 --- a/lib/compiler/src/v3_kernel_pp.erl +++ b/lib/compiler/src/v3_kernel_pp.erl @@ -104,20 +104,26 @@ format_1(#k_tuple{es=Es}, Ctxt) -> format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), $} ]; -format_1(#k_map{var=#k_var{}=Var,es=Es}, Ctxt) -> - [$~,${, +format_1(#k_map{var=#k_literal{val=M},op=assoc,es=Es}, Ctxt) when is_map(M), map_size(M) =:= 0 -> + ["~{", format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), - " | ",format_1(Var, Ctxt), - $},$~ + "}~" ]; -format_1(#k_map{op=assoc,es=Es}, Ctxt) -> +format_1(#k_map{var=#k_literal{val=M},op=exact,es=Es}, Ctxt) when is_map(M), map_size(M) =:= 0 -> + ["::{", + format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), + "}::" + ]; +format_1(#k_map{var=Var,op=assoc,es=Es}, Ctxt) -> ["~{", format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), + " | ",format_1(Var, Ctxt), "}~" ]; -format_1(#k_map{es=Es}, Ctxt) -> +format_1(#k_map{var=Var,op=exact,es=Es}, Ctxt) -> ["::{", format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), + " | ",format_1(Var, Ctxt), "}::" ]; format_1(#k_map_pair{key=K,val=V}, Ctxt) -> diff --git a/lib/compiler/src/v3_life.erl b/lib/compiler/src/v3_life.erl index c4f54a7970..cd4b5fd674 100644 --- a/lib/compiler/src/v3_life.erl +++ b/lib/compiler/src/v3_life.erl @@ -324,8 +324,7 @@ type(k_binary) -> binary; type(k_bin_seg) -> bin_seg; type(k_bin_int) -> bin_int; type(k_bin_end) -> bin_end; -type(k_map) -> map; -type(k_map_pair) -> map_pair. +type(k_map) -> map. %% variable(Klit) -> Lit. %% var_list([Klit]) -> [Lit]. diff --git a/lib/compiler/test/andor_SUITE.erl b/lib/compiler/test/andor_SUITE.erl index d79696df38..b5408ecd8f 100644 --- a/lib/compiler/test/andor_SUITE.erl +++ b/lib/compiler/test/andor_SUITE.erl @@ -171,6 +171,8 @@ t_and_or(Config) when is_list(Config) -> false = ?GUARD(erlang:'not'(erlang:'and'(bar, True))), false = ?GUARD(erlang:'not'(erlang:'not'(erlang:'and'(bar, True)))), + true = (fun (X = true) when X or true or X -> true end)(True), + ok. t_andalso(Config) when is_list(Config) -> diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index 149b9bbb8f..10e3451e8f 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -34,7 +34,7 @@ otp_7188/1,otp_7233/1,otp_7240/1,otp_7498/1, match_string/1,zero_width/1,bad_size/1,haystack/1, cover_beam_bool/1,matched_out_size/1,follow_fail_branch/1, - no_partition/1,calling_a_binary/1]). + no_partition/1,calling_a_binary/1,binary_in_map/1]). -export([coverage_id/1,coverage_external_ignore/2]). @@ -59,7 +59,7 @@ groups() -> matching_and_andalso,otp_7188,otp_7233,otp_7240, otp_7498,match_string,zero_width,bad_size,haystack, cover_beam_bool,matched_out_size,follow_fail_branch, - no_partition,calling_a_binary]}]. + no_partition,calling_a_binary,binary_in_map]}]. init_per_suite(Config) -> @@ -1189,6 +1189,26 @@ call_binary(<<>>, Acc) -> call_binary(<<H,T/bits>>, Acc) -> T(<<Acc/binary,H>>). +binary_in_map(Config) when is_list(Config) -> + ok = match_binary_in_map(#{key => <<42:8>>}), + {'EXIT',{{badmatch,#{key := 1}},_}} = + (catch match_binary_in_map(#{key => 1})), + {'EXIT',{{badmatch,#{key := <<1023:16>>}},_}} = + (catch match_binary_in_map(#{key => <<1023:16>>})), + {'EXIT',{{badmatch,#{key := <<1:8>>}},_}} = + (catch match_binary_in_map(#{key => <<1:8>>})), + {'EXIT',{{badmatch,not_a_map},_}} = + (catch match_binary_in_map(not_a_map)), + ok. + +match_binary_in_map(Map) -> + case 8 of + N -> + #{key := <<42:N>>} = Map, + ok + end. + + check(F, R) -> R = F(). diff --git a/lib/compiler/test/core_SUITE_data/map_core_test.core b/lib/compiler/test/core_SUITE_data/map_core_test.core index 7ece8a8bbd..2aa853d450 100644 --- a/lib/compiler/test/core_SUITE_data/map_core_test.core +++ b/lib/compiler/test/core_SUITE_data/map_core_test.core @@ -67,7 +67,7 @@ module 'map_core_test' ['map_core_test'/0, (Val, V) in let <_cor5> = %% Line 21 - M~{~<1337,_cor4>,~<'val',_cor2>}~ + ~{~<1337,_cor4>,~<'val',_cor2>|M}~ in %% Line 21 apply 'call'/2 (_cor5, Vs) @@ -92,4 +92,4 @@ module 'map_core_test' ['map_core_test'/0, fun (_cor0) -> call 'erlang':'get_module_info' ('map_core_test', _cor0) -end
\ No newline at end of file +end diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl index 8151dc1b16..2de17e7653 100644 --- a/lib/compiler/test/core_fold_SUITE.erl +++ b/lib/compiler/test/core_fold_SUITE.erl @@ -23,7 +23,7 @@ t_element/1,setelement/1,t_length/1,append/1,t_apply/1,bifs/1, eq/1,nested_call_in_case/1,guard_try_catch/1,coverage/1, unused_multiple_values_error/1,unused_multiple_values/1, - multiple_aliases/1]). + multiple_aliases/1,redundant_boolean_clauses/1,mixed_matching_clauses/1]). -export([foo/0,foo/1,foo/2,foo/3]). @@ -40,7 +40,7 @@ groups() -> [t_element,setelement,t_length,append,t_apply,bifs, eq,nested_call_in_case,guard_try_catch,coverage, unused_multiple_values_error,unused_multiple_values, - multiple_aliases]}]. + multiple_aliases,redundant_boolean_clauses,mixed_matching_clauses]}]. init_per_suite(Config) -> @@ -60,6 +60,12 @@ t_element(Config) when is_list(Config) -> X = make_ref(), ?line X = id(element(1, {X,y,z})), ?line b = id(element(2, {a,b,c,d})), + (fun() -> + case {a,#{k=>X}} of + {a,#{k:=X}}=Tuple -> + #{k:=X} = id(element(2, Tuple)) + end + end)(), %% No optimization, but should work. Tuple = id({x,y,z}), @@ -204,6 +210,16 @@ eq(Config) when is_list(Config) -> ?line ?CMP_DIFF(a, [a]), ?line ?CMP_DIFF(a, {1,2,3}), + ?CMP_SAME(#{a=>1.0,b=>2}, #{b=>2.0,a=>1}), + ?CMP_SAME(#{a=>[1.0],b=>[2]}, #{b=>[2.0],a=>[1]}), + + %% The rule for comparing keys are different in 17.x and 18.x. + %% Just test that the results are consistent. + Bool = id(#{1=>a}) == id(#{1.0=>a}), %Unoptimizable. + Bool = id(#{1=>a}) == #{1.0=>a}, %Optimizable. + Bool = #{1=>a} == #{1.0=>a}, %Optimizable. + io:format("Bool = ~p\n", [Bool]), + ok. %% OTP-7117. @@ -365,4 +381,23 @@ run_once() -> ok. +redundant_boolean_clauses(Config) when is_list(Config) -> + X = id(0), + yes = case X == 0 of + false -> no; + false -> no; + true -> yes + end. + +mixed_matching_clauses(Config) when is_list(Config) -> + 0 = case #{} of + #{} -> 0; + a -> 1 + end, + 0 = case <<>> of + <<>> -> 0; + a -> 1 + end, + ok. + id(I) -> I. diff --git a/lib/compiler/test/error_SUITE.erl b/lib/compiler/test/error_SUITE.erl index 5cdf429a5f..bd877bb528 100644 --- a/lib/compiler/test/error_SUITE.erl +++ b/lib/compiler/test/error_SUITE.erl @@ -23,7 +23,7 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, head_mismatch_line/1,warnings_as_errors/1, bif_clashes/1, - transforms/1,forbidden_maps/1]). + transforms/1,forbidden_maps/1,bad_utf8/1]). %% Used by transforms/1 test case. -export([parse_transform/2]). @@ -36,7 +36,8 @@ all() -> groups() -> [{p,test_lib:parallel(), - [head_mismatch_line,warnings_as_errors,bif_clashes,transforms,forbidden_maps]}]. + [head_mismatch_line,warnings_as_errors,bif_clashes, + transforms,forbidden_maps,bad_utf8]}]. init_per_suite(Config) -> Config. @@ -254,6 +255,23 @@ forbidden_maps(Config) when is_list(Config) -> [] = run2(Config, Ts1), ok. +bad_utf8(Config) -> + Ts = [{bad_utf8, + %% If coding is specified explicitly as utf-8, there should be + %% a compilation error; we must not fallback to parsing the + %% file in latin-1 mode. + <<"%% coding: utf-8 + %% Bj",246,"rn + t() -> \"",246,"\". + ">>, + [], + {error,[{2,epp,cannot_parse}, + {2,file_io_server,invalid_unicode}], + []} + }], + [] = run2(Config, Ts), + ok. + run(Config, Tests) -> ?line File = test_filename(Config), @@ -318,6 +336,7 @@ run_test(Test0, File, Warnings, WriteBeam) -> ?line compile:file(File, [binary,report|Warnings]), %% Test result of compilation. + io:format("~p\n", [Opts]), ?line Res = case compile:file(File, Opts) of {ok,Mod,_,[{_File,Ws}]} -> %io:format("compile:file(~s,~p) ->~n~p~n", @@ -335,6 +354,11 @@ run_test(Test0, File, Warnings, WriteBeam) -> %io:format("compile:file(~s,~p) ->~n~p~n", % [File,Opts,_ZZ]), {error,Es,Ws}; + {error,[{XFile,Es1},{XFile,Es2}],Ws} = _ZZ + when is_list(XFile) -> + %io:format("compile:file(~s,~p) ->~n~p~n", + % [File,Opts,_ZZ]), + {error,Es1++Es2,Ws}; {error,Es,[{_File,Ws}]} = _ZZ-> %io:format("compile:file(~s,~p) ->~n~p~n", % [File,Opts,_ZZ]), diff --git a/lib/compiler/test/fun_SUITE.erl b/lib/compiler/test/fun_SUITE.erl index 25b7f677b5..a3e9b7fe4e 100644 --- a/lib/compiler/test/fun_SUITE.erl +++ b/lib/compiler/test/fun_SUITE.erl @@ -21,10 +21,10 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, test1/1,overwritten_fun/1,otp_7202/1,bif_fun/1, - external/1,eep37/1,badarity/1]). + external/1,eep37/1,eep37_dup/1,badarity/1]). -%% Internal export. --export([call_me/1]). +%% Internal exports. +-export([call_me/1,dup1/0,dup2/0]). -include_lib("test_server/include/test_server.hrl"). @@ -32,7 +32,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> test_lib:recompile(?MODULE), - [test1,overwritten_fun,otp_7202,bif_fun,external,eep37,badarity]. + [test1,overwritten_fun,otp_7202,bif_fun,external,eep37,eep37_dup,badarity]. groups() -> []. @@ -206,6 +206,17 @@ eep37(Config) when is_list(Config) -> 50 = UnusedName(8), ok. +eep37_dup(Config) when is_list(Config) -> + dup1 = (dup1())(), + dup2 = (dup2())(), + ok. + +dup1() -> + fun _F() -> dup1 end. + +dup2() -> + fun _F() -> dup2 end. + badarity(Config) when is_list(Config) -> {'EXIT',{{badarity,{_,[]}},_}} = (catch (fun badarity/1)()), ok. diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl index eb205d09a7..34bfdeb1e5 100644 --- a/lib/compiler/test/guard_SUITE.erl +++ b/lib/compiler/test/guard_SUITE.erl @@ -1556,6 +1556,24 @@ bad_constants(Config) when is_list(Config) -> bad_guards(Config) when is_list(Config) -> if erlang:float(self()); true -> ok end, + + fc(catch bad_guards_1(1, [])), + fc(catch bad_guards_1(1, [2])), + fc(catch bad_guards_1(atom, [2])), + + fc(catch bad_guards_2(#{a=>0,b=>0}, [])), + fc(catch bad_guards_2(#{a=>0,b=>0}, [x])), + fc(catch bad_guards_2(not_a_map, [x])), + fc(catch bad_guards_2(42, [x])), + ok. + +%% beam_bool used to produce GC BIF instructions whose +%% Live operands included uninitialized registers. + +bad_guards_1(X, [_]) when {{X}}, -X -> + ok. + +bad_guards_2(M, [_]) when M#{a := 0, b => 0}, map_size(M) -> ok. %% Call this function to turn off constant propagation. diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl index b7e27afef1..403b7e8405 100644 --- a/lib/compiler/test/map_SUITE.erl +++ b/lib/compiler/test/map_SUITE.erl @@ -40,10 +40,13 @@ t_build_and_match_over_alloc/1, t_build_and_match_empty_val/1, t_build_and_match_val/1, + t_build_and_match_nil/1, + t_build_and_match_structure/1, %% errors in 17.0-rc1 t_update_values/1, - t_expand_map_update/1 + t_expand_map_update/1, + t_export/1 ]). suite() -> []. @@ -67,10 +70,13 @@ all() -> [ t_build_and_match_over_alloc, t_build_and_match_empty_val, t_build_and_match_val, + t_build_and_match_nil, + t_build_and_match_structure, %% errors in 17.0-rc1 t_update_values, - t_expand_map_update + t_expand_map_update, + t_export ]. groups() -> []. @@ -107,6 +113,10 @@ t_build_and_match_literals(Config) when is_list(Config) -> 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}), + %% map key + #{ #{} := 42 } = id(#{ #{} => 42 }), + #{ #{ "a" => 3 } := 42 } = id(#{ #{ "a" => 3} => 42 }), + %% nil key #{[]:=ok,1:=2} = id(#{[]=>ok,1=>2}), @@ -116,6 +126,8 @@ t_build_and_match_literals(Config) when is_list(Config) -> {'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"}))), + {'EXIT',{badarg,_}} = (catch id(#{<<0:258>> =>"three"})), + {'EXIT',{{badmatch,_},_}} = (catch (#{#{"a"=>42} := 3}=id(#{#{"a"=>3}=>42}))), ok. t_build_and_match_aliasing(Config) when is_list(Config) -> @@ -232,7 +244,8 @@ t_update_assoc(Config) when is_list(Config) -> %% Errors cases. BadMap = id(badmap), {'EXIT',{badarg,_}} = (catch BadMap#{nonexisting=>val}), - + {'EXIT',{badarg,_}} = (catch <<>>#{nonexisting=>val}), + {'EXIT',{badarg,_}} = (catch M0#{<<0:257>> => val}), %% limitation ok. t_update_exact(Config) when is_list(Config) -> @@ -260,6 +273,8 @@ t_update_exact(Config) when is_list(Config) -> {'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}), + {'EXIT',{badarg,_}} = (catch <<>>#{nonexisting:=val}), + {'EXIT',{badarg,_}} = (catch M0#{<<0:257>> := val}), %% limitation ok. t_update_values(Config) when is_list(Config) -> @@ -285,6 +300,12 @@ t_expand_map_update(Config) when is_list(Config) -> #{<<"hello">> := <<"les gens">>} = M, ok. +t_export(Config) when is_list(Config) -> + Raclette = id(#{}), + case brie of brie -> Fromage = Raclette end, + Raclette = Fromage#{}, + ok. + check_val(#{val1:=V1, val2:=V2},V1,V2) -> ok. get_val(#{ "wazzup" := _, val := V}) -> V; @@ -553,6 +574,32 @@ t_build_and_match_val(Config) when is_list(Config) -> test_server:fail({no_match, Other}) end. +t_build_and_match_nil(Config) when is_list(Config) -> + %% literals removed the coverage + V1 = id(cookie), + V2 = id(cake), + V3 = id(crisps), + + #{ [] := V1, "treat" := V2, {your,treat} := V3 } = id(#{ + {your,treat} => V3, + "treat" => V2, + [] => V1 }), + #{ [] := V3, [] := V3 } = id(#{ [] => V1, [] => V3 }), + ok. + +t_build_and_match_structure(Config) when is_list(Config) -> + V2 = id("it"), + S = id([42,{"hi", "=)", #{ "a" => 42, any => any, val => "get_" ++ V2}}]), + + %% match deep map values + V2 = case S of + [42,{"hi",_, #{ "a" := 42, val := "get_" ++ V1, any := _ }}] -> V1 + end, + %% match deep map + ok = case S of + [42,{"hi",_, #{ }}] -> ok + end, + ok. %% Use this function to avoid compile-time evaluation of an expression. id(I) -> I. diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl index ae7d764535..1e778dca24 100644 --- a/lib/compiler/test/match_SUITE.erl +++ b/lib/compiler/test/match_SUITE.erl @@ -22,7 +22,7 @@ init_per_group/2,end_per_group/2, pmatch/1,mixed/1,aliases/1,match_in_call/1, untuplify/1,shortcut_boolean/1,letify_guard/1, - selectify/1,underscore/1,coverage/1]). + selectify/1,underscore/1,match_map/1,coverage/1]). -include_lib("test_server/include/test_server.hrl"). @@ -35,7 +35,8 @@ all() -> groups() -> [{p,test_lib:parallel(), [pmatch,mixed,aliases,match_in_call,untuplify, - shortcut_boolean,letify_guard,selectify,underscore,coverage]}]. + shortcut_boolean,letify_guard,selectify, + underscore,match_map,coverage]}]. init_per_suite(Config) -> @@ -400,6 +401,24 @@ underscore(Config) when is_list(Config) -> _ = is_list(Config), ok. +-record(s, {map,t}). + +match_map(Config) when is_list(Config) -> + Map = #{key=>{x,y},ignore=>anything}, + #s{map=Map,t={x,y}} = do_match_map(#s{map=Map}), + {a,#{k:={a,b,c}}} = do_match_map_2(#{k=>{a,b,c}}), + ok. + +do_match_map(#s{map=#{key:=Val}}=S) -> + %% Would crash with a 'badarg' exception. + S#s{t=Val}. + +do_match_map_2(Map) -> + case {a,Map} of + {a,#{k:=_}}=Tuple -> + Tuple + end. + coverage(Config) when is_list(Config) -> %% Cover beam_dead. ok = coverage_1(x, a), diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl index f63299ea35..0637041873 100644 --- a/lib/compiler/test/warnings_SUITE.erl +++ b/lib/compiler/test/warnings_SUITE.erl @@ -37,8 +37,9 @@ -export([pattern/1,pattern2/1,pattern3/1,pattern4/1, guard/1,bad_arith/1,bool_cases/1,bad_apply/1, - files/1,effect/1,bin_opt_info/1,bin_construction/1, comprehensions/1, - maps/1]). + files/1,effect/1,bin_opt_info/1,bin_construction/1, + comprehensions/1,maps/1,redundant_boolean_clauses/1, + latin1_fallback/1]). % Default timetrap timeout (set in init_per_testcase). -define(default_timeout, ?t:minutes(2)). @@ -62,7 +63,8 @@ groups() -> [{p,test_lib:parallel(), [pattern,pattern2,pattern3,pattern4,guard, bad_arith,bool_cases,bad_apply,files,effect, - bin_opt_info,bin_construction,comprehensions,maps]}]. + bin_opt_info,bin_construction,comprehensions,maps, + redundant_boolean_clauses,latin1_fallback]}]. init_per_suite(Config) -> Config. @@ -201,6 +203,8 @@ pattern4(Config) when is_list(Config) -> [nowarn_unused_vars], {warnings, [{9,sys_core_fold,no_clause_match}, + {11,sys_core_fold,nomatch_shadow}, + {15,sys_core_fold,nomatch_shadow}, {18,sys_core_fold,no_clause_match}, {23,sys_core_fold,no_clause_match}, {33,sys_core_fold,no_clause_match} @@ -569,10 +573,111 @@ maps(Config) when is_list(Config) -> ">>, [], {warnings,[{3,sys_core_fold,no_clause_match}, - {9,sys_core_fold,nomatch_clause_type}]}}], + {9,sys_core_fold,nomatch_clause_type}]}}, + {bad_map_src1, + <<" + t() -> + M = {a,[]}, + {'EXIT',{badarg,_}} = (catch(M#{ a => 1})), + ok. + ">>, + [], + {warnings,[{4,v3_kernel,bad_map}]}}, + {bad_map_src2, + <<" + t() -> + M = id({a,[]}), + {'EXIT',{badarg,_}} = (catch(M#{ a => 1})), + ok. + id(I) -> I. + ">>, + [inline], + {warnings,[{4,v3_kernel,bad_map}]}}, + {bad_map_src3, + <<" + t() -> + {'EXIT',{badarg,_}} = (catch <<>>#{ a := 1}), + ok. + ">>, + [], + {warnings,[{3,v3_core,bad_map}]}}, + {bad_map_literal_key, + <<" + t() -> + V = id(1), + M = id(#{ <<$h,$i>> => V }), + V = case M of + #{ <<0:257>> := Val } -> Val; + #{ <<$h,$i>> := Val } -> Val + end, + ok. + id(I) -> I. + ">>, + [], + {warnings,[{6,v3_core,nomatch}]}}], run(Config, Ts), ok. +redundant_boolean_clauses(Config) when is_list(Config) -> + Ts = [{redundant_boolean_clauses, + <<" + t(X) -> + case X == 0 of + false -> no; + false -> no; + true -> yes + end. + ">>, + [], + {warnings,[{5,sys_core_fold,nomatch_shadow}]}}], + run(Config, Ts), + ok. + +latin1_fallback(Conf) when is_list(Conf) -> + DataDir = ?privdir, + IncFile = filename:join(DataDir, "include_me.hrl"), + file:write_file(IncFile, <<"%% ",246," in include file\n">>), + Ts1 = [{latin1_fallback1, + %% Test that the compiler fall backs to latin-1 with + %% a warning if a file has no encoding and does not + %% contain correct UTF-8 sequences. + <<"%% Bj",246,"rn + t(_) -> \"",246,"\"; + t(x) -> ok. + ">>, + [], + {warnings,[{1,compile,reparsing_invalid_unicode}, + {3,sys_core_fold,{nomatch_shadow,2}}]}}], + [] = run(Conf, Ts1), + + Ts2 = [{latin1_fallback2, + %% Test that the compiler fall backs to latin-1 with + %% a warning if a file has no encoding and does not + %% contain correct UTF-8 sequences. + <<" + + -include(\"include_me.hrl\"). + ">>, + [], + {warnings,[{1,compile,reparsing_invalid_unicode}]} + }], + [] = run(Conf, Ts2), + + Ts3 = [{latin1_fallback3, + %% Test that the compiler fall backs to latin-1 with + %% a warning if a file has no encoding and does not + %% contain correct UTF-8 sequences. + <<"-ifdef(NOTDEFINED). + t(_) -> \"",246,"\"; + t(x) -> ok. + -endif. + ">>, + [], + {warnings,[{2,compile,reparsing_invalid_unicode}]}}], + [] = run(Conf, Ts3), + + ok. + %%% %%% End of test cases. %%% diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk index cbdf57f177..b1a6c15ac9 100644 --- a/lib/compiler/vsn.mk +++ b/lib/compiler/vsn.mk @@ -1 +1 @@ -COMPILER_VSN = 4.9.4 +COMPILER_VSN = 5.0.3 |