From ec6c392396cfa7f823b31ba9c623847c9c7b5553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Thu, 27 Oct 2022 10:39:16 +0200 Subject: [PATCH] Lift restrictions for matching of binaries and maps There has always been an implementation limitation for matching of binaries (for technical reasons). For example: foo(Bin) -> <> = <> = Bin, {A,X,Y}. This would fail to compile with the following message: t.erl:5:5: binary patterns cannot be matched in parallel using '=' % 5| <> = <> = Bin, % | ^ This commit lifts this restriction, making the example legal. A restriction for map matching is also lifted, but before we can describe that, we'll need a digression to talk about the `=` operator. The `=` operator can be used for two similar but slightly differently purposes. When used in a pattern, for example in a function head, both the left-hand and right-hand side operands must be patterns: Pattern1 = Pattern2 For example: bar(#{a := A} = #{b := B}) -> {A, B}. The following example will not compile because the right-hand side is not a pattern but an expression: wrong(#{a := A} = #{b => B}) -> {A, B}. t.erl:4:23: illegal pattern % 4| wrong(#{a := A} = #{b => B}) -> {A, B}. % | ^ Used in this context, the `=` operator does not imply that the two patterns are matched in any particular order. Attempting to use a variable matched out on the left-hand side on the right-hand side, or vice versa, will fail: also_wrong1(#{B := A} = #{b := B}) -> {A,B}. also_wrong2(#{a := A} = #{A := B}) -> {A,B}. t.erl:6:15: variable 'B' is unbound % 6| also_wrong1(#{B := A} = #{b := B}) -> {A,B}. % | ^ t.erl:7:27: variable 'A' is unbound % 7| also_wrong2(#{a := A} = #{A := B}) -> {A,B}. % | ^ The other way to use `=` is in a function body. Used in this way, the right-hand side must be an expression: Pattern = Expression For example: foobar(Value) -> #{a := A} = #{a => Value}, A. Used in this context, the right-hand side of `=` must **not** be a pattern: illegal_foobar(Value) -> #{a := A} = #{a := Value}, A. t.erl:18:21: only association operators '=>' are allowed in map construction % 18| #{a := A} = #{a := Value}, % | ^ When used in a body context, the value of the `=` operator is the value of its right-hand side operand. When multiple `=` operators are combined, they are evaluted from right to left. That means that any number of patterns can be matched at once: Pattern1 = Pattern2 = ... = PatternN = Expr Given that there is a well-defined evaluation order, one would except that the following example would be legal: baz(M) -> #{K := V} = #{k := K} = M, V. It is not. In Erlang/OTP 25 or earlier, the compilation fails with the following message: t.erl:28:7: variable 'K' is unbound % 28| #{K := V} = #{k := K} = M, % | ^ That restriction is now lifted, making the example legal. Closes #6348 --- lib/compiler/src/v3_core.erl | 83 ++++++++--- lib/compiler/test/bs_match_SUITE.erl | 97 ++++++++++++- lib/compiler/test/map_SUITE.erl | 16 ++- lib/stdlib/src/erl_lint.erl | 115 +-------------- lib/stdlib/test/erl_eval_SUITE.erl | 35 ++++- lib/stdlib/test/erl_lint_SUITE.erl | 148 +++++++++++++++----- lib/stdlib/test/qlc_SUITE.erl | 3 +- system/doc/reference_manual/expressions.xml | 20 ++- 8 files changed, 334 insertions(+), 183 deletions(-) diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index 16b3ac340f79..17affd21b46d 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -2025,10 +2025,18 @@ is_safe(_) -> false. %% fold_match(MatchExpr, Pat) -> {MatchPat,Expr}. %% Fold nested matches into one match with aliased patterns. -fold_match({match,L,P0,E0}, P) -> - {P1,E1} = fold_match(E0, P), - {{match,L,P0,P1},E1}; -fold_match(E, P) -> {P,E}. +fold_match(P, E) -> + fold_match_1(P, [{0,E}]). + +fold_match_1({match,L,P0,E0}, Acc) -> + fold_match_1(E0, [{L,P0}|Acc]); +fold_match_1(E, Acc) -> + {fold_match_2(Acc),E}. + +fold_match_2([{_L,P}]) -> + P; +fold_match_2([{L,P}|Ps]) -> + {match,L,P,fold_match_2(Ps)}. %% pattern(Pattern, State) -> {CorePat,[PreExp],State}. %% Transform a pattern by removing line numbers. We also normalise @@ -2191,17 +2199,26 @@ pat_alias(#c_alias{var=#c_var{name=V1}=Var1,pat=P1}, pat_alias(#c_alias{var=#c_var{}=Var,pat=P1}, P2) -> #c_alias{var=Var,pat=pat_alias(P1, P2)}; -pat_alias(#imap{es=Es1}=M, #imap{es=Es2}) -> - M#imap{es=pat_alias_map_pairs(Es1 ++ Es2)}; +pat_alias(#imap{es=LEs}=M, #imap{es=REs0}) -> + [E0|REs] = REs0, + #imappair{anno=#a{anno=Anno}=A} = E0, + E = E0#imappair{anno=A#a{anno=[parallel_match|Anno]}}, + M#imap{es=LEs++[E|REs]}; pat_alias(P1, #c_var{}=Var) -> #c_alias{var=Var,pat=P1}; pat_alias(P1, #c_alias{pat=P2}=Alias) -> Alias#c_alias{pat=pat_alias(P1, P2)}; +pat_alias(#ibinary{segments=[]}=P, #ibinary{segments=[]}) -> + P; +pat_alias(#ibinary{segments=[_|_]=Segs1}=P, #ibinary{segments=[S0|Segs2]}) -> + #ibitstr{anno=#a{anno=Anno}=A} = S0, + S = S0#ibitstr{anno=A#a{anno=[parallel_match|Anno]}}, + P#ibinary{segments=Segs1++[S|Segs2]}; + pat_alias(P1, P2) -> - %% Aliases between binaries are not allowed, so the only - %% legal patterns that remain are data patterns. + %% The only legal patterns that remain are data patterns. case cerl:is_data(P1) andalso cerl:is_data(P2) of false -> throw(nomatch); true -> ok @@ -3635,9 +3652,14 @@ split_pat(#c_binary{segments=Segs0}=Bin, St0) -> case split_bin_segments(Segs0, Vars, St0, []) of none -> none; - {TailVar,Wrap,Bef,Aft,St} -> + {size_var,TailVar,Wrap,Bef,Aft,St} -> BefBin = Bin#c_binary{segments=Bef}, - {BefBin,{split,[TailVar],Wrap,Bin#c_binary{segments=Aft},nil},St} + {BefBin,{split,[TailVar],Wrap,Bin#c_binary{segments=Aft},nil},St}; + {parallel_match,Bef,Aft,St1} -> + {BinVar,St} = new_var(St1), + BefBin = #c_alias{var=BinVar,pat=Bin#c_binary{segments=Bef}}, + Wrap = fun(Body) -> Body end, + {BefBin,{split,[BinVar],Wrap,Bin#c_binary{segments=Aft},nil},St} end; split_pat(#c_map{es=Es}=Map, St) -> split_map_pat(Es, Map, St, []); @@ -3657,7 +3679,21 @@ split_pat(Data, St0) -> Es = cerl:data_es(Data), split_data(Es, Type, St0, []). -split_map_pat([#c_map_pair{key=Key,val=Val}=E0|Es], Map0, St0, Acc) -> +split_map_pat([#c_map_pair{anno=Anno0}=E0|Es], Map, St0, Acc) -> + case member(parallel_match, Anno0) of + true -> + Anno = Anno0 -- [parallel_match], + E = E0#c_map_pair{anno=Anno}, + {MapVar,St} = new_var(St0), + BefMap = #c_alias{var=MapVar,pat=Map#c_map{es=reverse(Acc)}}, + Wrap = fun(Body) -> Body end, + {BefMap,{split,[MapVar],Wrap,Map#c_map{es=[E|Es]},nil},St}; + false -> + split_map_pat_1(E0, Es, Map, St0, Acc) + end; +split_map_pat([], _, _, _) -> none. + +split_map_pat_1(#c_map_pair{key=Key,val=Val}=E0, Es, Map0, St0, Acc) -> case eval_map_key(Key, E0, Es, Map0, St0) of none -> case split_pat(Val, St0) of @@ -3673,8 +3709,7 @@ split_map_pat([#c_map_pair{key=Key,val=Val}=E0|Es], Map0, St0, Acc) -> BefMap0 = Map0#c_map{es=reverse(Acc)}, BefMap = #c_alias{var=MapVar,pat=BefMap0}, {BefMap,Split,St1} - end; -split_map_pat([], _, _, _) -> none. + end. eval_map_key(#c_var{}, _E, _Es, _Map, _St) -> none; @@ -3719,7 +3754,19 @@ split_data([E|Es0], Type, St0, Acc) -> end; split_data([], _, _, _) -> none. -split_bin_segments([#c_bitstr{val=Val,size=Size}=S0|Segs], Vars0, St0, Acc) -> +split_bin_segments([#c_bitstr{anno=Anno0}=S0|Segs], Vars, St, Acc) -> + case member(parallel_match, Anno0) of + true -> + Anno = Anno0 -- [parallel_match], + S = S0#c_bitstr{anno=Anno}, + {parallel_match,reverse(Acc),[S|Segs],St}; + false -> + split_bin_segments_1(S0, Segs, Vars, St, Acc) + end; +split_bin_segments(_, _, _, _) -> + none. + +split_bin_segments_1(#c_bitstr{val=Val,size=Size}=S0, Segs, Vars0, St0, Acc) -> Vars = case Val of #c_var{name=V} -> gb_sets:add(V, Vars0); _ -> Vars0 @@ -3736,7 +3783,7 @@ split_bin_segments([#c_bitstr{val=Val,size=Size}=S0|Segs], Vars0, St0, Acc) -> %% in the same pattern. {TailVar,Tail,St} = split_tail_seg(S0, Segs, St0), Wrap = fun(Body) -> Body end, - {TailVar,Wrap,reverse(Acc, [Tail]),[S0|Segs],St}; + {size_var,TailVar,Wrap,reverse(Acc, [Tail]),[S0|Segs],St}; false -> split_bin_segments(Segs, Vars, St0, [S0|Acc]) end; @@ -3748,10 +3795,8 @@ split_bin_segments([#c_bitstr{val=Val,size=Size}=S0|Segs], Vars0, St0, Acc) -> {SizeVar,St2} = new_var(St1), S = S0#c_bitstr{size=SizeVar}, {Wrap,St3} = split_wrap(SizeVar, Size, St2), - {TailVar,Wrap,reverse(Acc, [Tail]),[S|Segs],St3} - end; -split_bin_segments(_, _, _, _) -> - none. + {size_var,TailVar,Wrap,reverse(Acc, [Tail]),[S|Segs],St3} + end. split_tail_seg(#c_bitstr{anno=A}=S, Segs, St0) -> {TailVar,St} = new_var(St0), diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index 2f5b76406713..e55143905273 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -49,7 +49,8 @@ bad_phi_paths/1,many_clauses/1, combine_empty_segments/1,hangs_forever/1, bs_saved_position_units/1,empty_matches/1, - trim_bs_start_match_resume/1]). + trim_bs_start_match_resume/1, + binary_aliases/1]). -export([coverage_id/1,coverage_external_ignore/2]). @@ -89,7 +90,8 @@ groups() -> exceptions_after_match_failure,bad_phi_paths, many_clauses,combine_empty_segments,hangs_forever, bs_saved_position_units,empty_matches, - trim_bs_start_match_resume]}]. + trim_bs_start_match_resume, + binary_aliases]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -2495,8 +2497,6 @@ trim_bs_start_match_resume_1(<>) -> _ = id(Context), Context. -id(I) -> I. - expand_and_squeeze(Config) when is_list(Config) -> %% UTF8 literals are expanded and then squeezed into integer16 ensure_squeezed(16, [?Q("<<$รก/utf8,_/binary>>"), @@ -2634,3 +2634,92 @@ many_clauses(_Config) -> one_clause(I) -> ?Q(<<"{_@I@,<>} -> _@I@ + Val">>). + +%% GH-6348/OTP-18297: Allow aliases for binaries. +-record(ba_foo, {a,b,c}). + +binary_aliases(_Config) -> + F1 = fun(<> = <>) -> {A,B} end, + {42,42} = F1(id(<<42>>)), + {99,99} = F1(id(<<99>>)), + + F2 = fun(#ba_foo{a = <>} = #ba_foo{a = <>}) -> {X,Y} end, + {255,255} = F2(id(#ba_foo{a = <<-1>>})), + {107,107} = F2(id(#ba_foo{a = <<107>>})), + + F3 = fun(#ba_foo{a = <>} = #ba_foo{a = <>}) -> {X,Y,Z} end, + {255,15,15} = F3(id(#ba_foo{a = <<-1>>})), + {16#5c,16#5,16#c} = F3(id(#ba_foo{a = <<16#5c>>})), + + F4 = fun([<> = {C,D} = <>]) -> + {A,B,C,D}; + (L) -> + lists:sum(L) + end, + 6 = F4(id([1,2,3])), + + F5 = fun(Val) -> + <> = X = <> = Val, + {A,B,X} + end, + {42,42,<<42>>} = F5(id(<<42>>)), + + F6 = fun(X, Y) -> + <> = <>, + A + end, + 16#7c = F6(16#7, 16#c), + 16#ed = F6(16#e, 16#d), + + F7 = fun(Val) -> + (<> = X) = (<> = <>) = Val, + {A,B,X} + end, + {0,0,<<0>>} = F7(id(<<0>>)), + {'EXIT',{{badmatch,<<1>>},_}} = catch F7(<<1>>), + + F8 = fun(Val) -> + (<> = X) = (Y = <>) = Val, + {A,B,X,Y} + end, + {253,253,<<253>>,<<253>>} = F8(id(<<253>>)), + + F9 = fun(Val) -> + (Z = <> = X) = (Y = <> = W) = Val, + {A,B,X,Y,Z,W} + end, + {201,201,<<201>>,<<201>>,<<201>>,<<201>>} = F9(id(<<201>>)), + + F10 = fun(X) -> + <<>> = (<<>> = X) + end, + <<>> = F10(id(<<>>)), + {'EXIT',{{badmatch,42},_}} = catch F10(id(42)), + + F11 = fun(Bin) -> + <> = <> = <> = Bin, + {A,B,C,D,E,F,G,H} + end, + {<<0>>,<<0,0,0>>, 0,0, 0,0,0,0} = F11(id(<<0:32>>)), + {<<16#ab>>,<<16#cdef57:24>>, 16#abcd,16#ef57, 16#ab,16#cd,16#ef,16#57} = + F11(id(<<16#abcdef57:32>>)), + + F12 = fun(#{key := <>} = #{key := <>}) -> {X,Y} end, + {255,255} = F12(id(#{key => <<-1>>})), + {209,209} = F12(id(#{key => <<209>>})), + + F13 = fun(Bin) -> + <<_:8,A:Size>> = <<_:8,B:Size/bits>> = <> = Bin, + {Size,A,B} + end, + {0,0,<<>>} = F13(id(<<0>>)), + {1,1,<<1:1>>} = F13(id(<<1,1:1>>)), + {8,42,<<42>>} = F13(id(<<8,42>>)), + + ok. + + +%%% Common utilities. + +id(I) -> I. + diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl index 8716d85477c0..bdfc0346d9e4 100644 --- a/lib/compiler/test/map_SUITE.erl +++ b/lib/compiler/test/map_SUITE.erl @@ -88,7 +88,8 @@ %% miscellaneous t_conflicting_destinations/1, t_cse_assoc/1, - shared_key_tuples/1 + shared_key_tuples/1, + map_aliases/1 ]). -define(badmap(V, F, Args), {'EXIT', {{badmap,V}, [{maps,F,Args,_}|_]}}). @@ -163,7 +164,8 @@ all() -> %% miscellaneous t_conflicting_destinations, t_cse_assoc, - shared_key_tuples + shared_key_tuples, + map_aliases ]. groups() -> []. @@ -2567,6 +2569,16 @@ shared_key_tuples(_Config) -> decimal(Int) -> #{type => decimal, int => Int, exp => 0}. +%% GH-6348/OTP-18297: Extend parallel matching of maps. +map_aliases(_Config) -> + F1 = fun(M) -> + #{K := V} = #{k := {a,K}} = M, + V + end, + value = F1(#{k => {a,key}, key => value}), + + ok. + %% aux rand_terms(0) -> []; diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index da0b6c67b8c8..ad18e63cae54 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -315,8 +315,6 @@ format_error({too_many_arguments,Arity}) -> %% --- patterns and guards --- format_error(illegal_pattern) -> "illegal pattern"; format_error(illegal_map_key) -> "illegal map key in pattern"; -format_error(illegal_bin_pattern) -> - "binary patterns cannot be matched in parallel using '='"; format_error(illegal_expr) -> "illegal expression"; format_error({illegal_guard_local_call, {F,A}}) -> io_lib:format("call to local/imported function ~tw/~w is illegal in guard", @@ -1749,10 +1747,9 @@ pattern({op,_Anno,'++',{string,_Ai,_S},R}, Vt, Old, St) -> pattern({match,_Anno,Pat1,Pat2}, Vt0, Old, St0) -> {Lvt, Lnew, St1} = pattern(Pat1, Vt0, Old, St0), {Rvt, Rnew, St2} = pattern(Pat2, Vt0, Old, St1), - St3 = reject_invalid_alias(Pat1, Pat2, Vt0, St2), - {Vt1, St4} = vtmerge_pat(Lvt, Rvt, St3), - {New, St5} = vtmerge_pat(Lnew, Rnew, St4), - {Vt1, New, St5}; + {Vt1, St3} = vtmerge_pat(Lvt, Rvt, St2), + {New, St4} = vtmerge_pat(Lnew, Rnew, St3), + {Vt1, New, St4}; %% Catch legal constant expressions, including unary +,-. pattern(Pat, _Vt, _Old, St) -> case is_pattern_expr(Pat) of @@ -1779,101 +1776,6 @@ check_multi_field_init(Fs, Anno, Fields, St) -> false -> St end. -%% reject_invalid_alias(Pat, Expr, Vt, St) -> St' -%% Reject aliases for binary patterns at the top level. -%% Reject aliases for maps patterns at the top level. -%% The variables table (Vt) are for maps checkking. - -reject_invalid_alias_expr({bin,_,_}=P, {match,_,P0,E}, Vt, St0) -> - St = reject_invalid_alias(P, P0, Vt, St0), - reject_invalid_alias_expr(P, E, Vt, St); -reject_invalid_alias_expr({map,_,_}=P, {match,_,P0,E}, Vt, St0) -> - St = reject_invalid_alias(P, P0, Vt, St0), - reject_invalid_alias_expr(P, E, Vt, St); -reject_invalid_alias_expr({match,_,_,_}=P, {match,_,P0,E}, Vt, St0) -> - St = reject_invalid_alias(P, P0, Vt, St0), - reject_invalid_alias_expr(P, E, Vt, St); -reject_invalid_alias_expr(_, _, _, St) -> St. - - - -%% reject_invalid_alias(Pat1, Pat2, St) -> St' -%% Aliases of binary patterns, such as <> = <> or even -%% <> = <>, are not allowed. Traverse the patterns in parallel -%% and generate an error if any binary aliases are found. -%% We generate an error even if is obvious that the overall pattern can't -%% possibly match, for instance, {a,<>,c}={x,<>} WILL generate an -%% error. -%% Maps should reject unbound variables here. - -reject_invalid_alias({bin,Anno,_}, {bin,_,_}, _, St) -> - add_error(Anno, illegal_bin_pattern, St); -reject_invalid_alias({map,_Anno,Ps1}, {map,_,Ps2}, Vt, St0) -> - Fun = fun ({map_field_exact,_,{var,A,K},_V}, Sti) -> - case is_var_bound(K,Vt) of - true -> - Sti; - false -> - add_error(A, {unbound_var,K}, Sti) - end; - ({map_field_exact,_A,_K,_V}, Sti) -> - Sti - end, - foldl(Fun, foldl(Fun, St0, Ps1), Ps2); -reject_invalid_alias({cons,_,H1,T1}, {cons,_,H2,T2}, Vt, St0) -> - St = reject_invalid_alias(H1, H2, Vt, St0), - reject_invalid_alias(T1, T2, Vt, St); -reject_invalid_alias({tuple,_,Es1}, {tuple,_,Es2}, Vt, St) -> - reject_invalid_alias_list(Es1, Es2, Vt, St); -reject_invalid_alias({record,_,Name1,Pfs1}, {record,_,Name2,Pfs2}, Vt, - #lint{records=Recs}=St) -> - case Recs of - #{Name1 := {_Anno1,Fields1}, Name2 := {_Anno2,Fields2}} -> - reject_invalid_alias_rec(Pfs1, Pfs2, Fields1, Fields2, Vt, St); - #{} -> - %% One or more non-existing records. (An error messages has - %% already been generated, so we are done here.) - St - end; -reject_invalid_alias({match,_,P1,P2}, P, Vt, St0) -> - St = reject_invalid_alias(P1, P, Vt, St0), - reject_invalid_alias(P2, P, Vt, St); -reject_invalid_alias(P, {match,_,_,_}=M, Vt, St) -> - reject_invalid_alias(M, P, Vt, St); -reject_invalid_alias(_P1, _P2, _Vt, St) -> St. - -reject_invalid_alias_list([E1|Es1], [E2|Es2], Vt, St0) -> - St = reject_invalid_alias(E1, E2, Vt, St0), - reject_invalid_alias_list(Es1, Es2, Vt, St); -reject_invalid_alias_list(_, _, _, St) -> St. - -reject_invalid_alias_rec(PfsA0, PfsB0, FieldsA0, FieldsB0, Vt, St) -> - %% We treat records as if they have been converted to tuples. - PfsA1 = rbia_field_vars(PfsA0), - PfsB1 = rbia_field_vars(PfsB0), - FieldsA1 = rbia_fields(lists:reverse(FieldsA0), 0, []), - FieldsB1 = rbia_fields(lists:reverse(FieldsB0), 0, []), - FieldsA = sofs:relation(FieldsA1), - PfsA = sofs:relation(PfsA1), - A = sofs:join(FieldsA, 1, PfsA, 1), - FieldsB = sofs:relation(FieldsB1), - PfsB = sofs:relation(PfsB1), - B = sofs:join(FieldsB, 1, PfsB, 1), - C = sofs:join(A, 2, B, 2), - D = sofs:projection({external,fun({_,_,P1,_,P2}) -> {P1,P2} end}, C), - E = sofs:to_external(D), - {Ps1,Ps2} = lists:unzip(E), - reject_invalid_alias_list(Ps1, Ps2, Vt, St). - -rbia_field_vars(Fs) -> - [{Name,Pat} || {record_field,_,{atom,_,Name},Pat} <- Fs]. - -rbia_fields([{record_field,_,{atom,_,Name},_}|Fs], I, Acc) -> - rbia_fields(Fs, I+1, [{Name,I}|Acc]); -rbia_fields([_|Fs], I, Acc) -> - rbia_fields(Fs, I+1, Acc); -rbia_fields([], _, Acc) -> Acc. - %% is_pattern_expr(Expression) -> boolean(). %% Test if a general expression is a valid pattern expression. @@ -2620,8 +2522,7 @@ expr({'catch',Anno,E}, Vt, St0) -> {vtupdate(vtunsafe({'catch',Anno}, Evt, Vt), Evt),St}; expr({match,_Anno,P,E}, Vt, St0) -> {Evt,St1} = expr(E, Vt, St0), - {Pvt,Pnew,St2} = pattern(P, vtupdate(Evt, Vt), St1), - St = reject_invalid_alias_expr(P, E, Vt, St2), + {Pvt,Pnew,St} = pattern(P, vtupdate(Evt, Vt), St1), {vtupdate(Pnew, vtmerge(Evt, Pvt)),St}; expr({maybe_match,Anno,P,E}, Vt, St0) -> expr({match,Anno,P,E}, Vt, St0); @@ -3973,14 +3874,6 @@ warn_unused_vars(U, Vt, St0) -> UVt = map(fun ({V,{State,_,As}}) -> {V,{State,used,As}} end, U), {vtmerge(Vt, UVt), St1}. - -is_var_bound(V, Vt) -> - case orddict:find(V, Vt) of - {ok,{bound,_Usage,_}} -> true; - _ -> false - end. - - %% vtupdate(UpdVarTable, VarTable) -> VarTable. %% Add the variables in the updated vartable to VarTable. The variables %% will be updated with their property in UpdVarTable. The state of diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl index faaa9f727fc6..bd76fc5a89f2 100644 --- a/lib/stdlib/test/erl_eval_SUITE.erl +++ b/lib/stdlib/test/erl_eval_SUITE.erl @@ -55,7 +55,8 @@ otp_14708/1, otp_16545/1, otp_16865/1, - eep49/1]). + eep49/1, + binary_and_map_aliases/1]). %% %% Define to run outside of test server @@ -96,7 +97,7 @@ all() -> otp_8133, otp_10622, otp_13228, otp_14826, funs, custom_stacktrace, try_catch, eval_expr_5, zero_width, eep37, eep43, otp_15035, otp_16439, otp_14708, otp_16545, otp_16865, - eep49]. + eep49, binary_and_map_aliases]. groups() -> []. @@ -1971,6 +1972,36 @@ eep49(Config) when is_list(Config) -> {else_clause,simply_wrong}), ok. +%% GH-6348/OTP-18297: Lift restrictions for matching of binaries and maps. +binary_and_map_aliases(Config) when is_list(Config) -> + check(fun() -> + <> = <> = <<16#cafe:16>>, + {A,B,C} + end, + "begin <> = <> = <<16#cafe:16>>, {A,B,C} end.", + {16#cafe,16#ca,16#fe}), + check(fun() -> + <> = + <> = + <> = + <<16#abcdef57:32>>, + {A,B,C,D,E,F,G,H} + end, + "begin <> = + <> = + <> = + <<16#abcdef57:32>>, + {A,B,C,D,E,F,G,H} + end.", + {<<16#ab>>,<<16#cdef57:24>>, 16#abcd,16#ef57, 16#ab,16#cd,16#ef,16#57}), + check(fun() -> + #{K := V} = #{k := K} = #{k => my_key, my_key => 42}, + V + end, + "begin #{K := V} = #{k := K} = #{k => my_key, my_key => 42}, V end.", + 42), + ok. + %% Check the string in different contexts: as is; in fun; from compiled code. check(F, String, Result) -> check1(F, String, Result), diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl index 2f2f0fc23e23..83ed47e94f5f 100644 --- a/lib/stdlib/test/erl_lint_SUITE.erl +++ b/lib/stdlib/test/erl_lint_SUITE.erl @@ -49,7 +49,8 @@ unsafe_vars_try/1, unsized_binary_in_bin_gen_pattern/1, guard/1, otp_4886/1, otp_4988/1, otp_5091/1, otp_5276/1, otp_5338/1, - otp_5362/1, otp_5371/1, otp_7227/1, otp_5494/1, otp_5644/1, otp_5878/1, + otp_5362/1, otp_5371/1, otp_7227/1, binary_aliases/1, + otp_5494/1, otp_5644/1, otp_5878/1, otp_5917/1, otp_6585/1, otp_6885/1, otp_10436/1, otp_11254/1, otp_11772/1, otp_11771/1, otp_11872/1, export_all/1, @@ -91,7 +92,7 @@ all() -> unsafe_vars, unsafe_vars2, unsafe_vars_try, guard, unsized_binary_in_bin_gen_pattern, otp_4886, otp_4988, otp_5091, otp_5276, otp_5338, - otp_5362, otp_5371, otp_7227, otp_5494, otp_5644, + otp_5362, otp_5371, otp_7227, binary_aliases, otp_5494, otp_5644, otp_5878, otp_5917, otp_6585, otp_6885, otp_10436, otp_11254, otp_11772, otp_11771, otp_11872, export_all, bif_clash, behaviour_basic, behaviour_multiple, otp_11861, @@ -168,7 +169,13 @@ c(A) -> g({M, F}) -> (Z=M):(Z=F)(); g({M, F, Arg}) -> (Z=M):F(Z=Arg). h(X, Y) -> (Z=X) + (Z=Y).">>, - [warn_unused_vars], []}], + [warn_unused_vars], []}, + {basic3, + <<"f(E) -> + X = Y = E.">>, + [warn_unused_vars], + {warnings,[{{2,19},erl_lint,{unused_var,'X'}}, + {{2,23},erl_lint,{unused_var,'Y'}}]}}], [] = run(Config, Ts), ok. @@ -299,7 +306,7 @@ unused_vars_warn_lc(Config) when is_list(Config) -> j(X) -> [foo || X <- X, % X shadowed. X <- % X shadowed. X unused. - X = + X = Y = [[1,2,3]], % Y unused. X <- [], % X shadowed. X <- X]. % X shadowed. X unused. @@ -2286,22 +2293,22 @@ otp_15456(Config) when is_list(Config) -> ok. %% OTP-5371. Aliases for bit syntax expressions are no longer allowed. +%% GH-6348/OTP-18297: Updated for OTP 26 to allow aliases. otp_5371(Config) when is_list(Config) -> Ts = [{otp_5371_1, <<"t(<> = <>) -> {A,B}. ">>, [], - {errors,[{{1,23},erl_lint,illegal_bin_pattern}],[]}}, + []}, {otp_5371_2, <<"x([<>] = [<>]) -> {A,B}. y({a,<>} = {b,<>}) -> {A,B}. ">>, - [], - {errors,[{{1,24},erl_lint,illegal_bin_pattern}, - {{3,20},erl_lint,illegal_bin_pattern}],[]}}, + [], + {warnings,[{{3,15},v3_core,{nomatch,pattern}}]}}, {otp_5371_3, <<"-record(foo, {a,b,c}). -record(bar, {x,y,z}). @@ -2318,11 +2325,10 @@ otp_5371(Config) when is_list(Config) -> {X,Y}. ">>, [], - {errors,[{{4,26},erl_lint,illegal_bin_pattern}, - {{6,26},erl_lint,illegal_bin_pattern}, - {{8,26},erl_lint,illegal_bin_pattern}, - {{10,30},erl_lint,illegal_bin_pattern}, - {{12,30},erl_lint,illegal_bin_pattern}],[]}}, + {warnings,[{{4,15},v3_core,{nomatch,pattern}}, + {{8,15},v3_core,{nomatch,pattern}}, + {{10,15},v3_core,{nomatch,pattern}}, + {{12,15},v3_core,{nomatch,pattern}}]}}, {otp_5371_4, <<"-record(foo, {a,b,c}). -record(bar, {x,y,z}). @@ -2342,42 +2348,41 @@ otp_5371(Config) when is_list(Config) -> [] = run(Config, Ts), ok. -%% OTP_7227. Some aliases for bit syntax expressions were still allowed. +%% OTP-7227. Some aliases for bit syntax expressions were still allowed. +%% GH-6348/OTP-18297: Updated for OTP 26 to allow aliases. otp_7227(Config) when is_list(Config) -> Ts = [{otp_7227_1, <<"t([<> = {C,D} = <>]) -> {A,B,C,D}. ">>, [], - {errors,[{{1,42},erl_lint,illegal_bin_pattern}],[]}}, + {warnings,[{{1,21},v3_core,{nomatch,pattern}}]}}, {otp_7227_2, <<"t([(<> = {C,D}) = <>]) -> {A,B,C,D}. ">>, [], - {errors,[{{1,25},erl_lint,illegal_bin_pattern}],[]}}, + {warnings,[{{1,21},v3_core,{nomatch,pattern}}]}}, {otp_7227_3, <<"t([(<> = {C,D}) = (<> = <>)]) -> {A,B,C,D}. ">>, [], - {errors,[{{1,45},erl_lint,illegal_bin_pattern}, - {{1,45},erl_lint,illegal_bin_pattern}, - {{1,55},erl_lint,illegal_bin_pattern}],[]}}, + {warnings,[{{1,21},v3_core,{nomatch,pattern}}]}}, {otp_7227_4, <<"t(Val) -> <> = <> = Val, {A,B}. ">>, [], - {errors,[{{2,19},erl_lint,illegal_bin_pattern}],[]}}, + []}, {otp_7227_5, <<"t(Val) -> <> = X = <> = Val, {A,B,X}. ">>, [], - {errors,[{{2,19},erl_lint,illegal_bin_pattern}],[]}}, + []}, {otp_7227_6, <<"t(X, Y) -> <> = <>, @@ -2391,27 +2396,70 @@ otp_7227(Config) when is_list(Config) -> {A,B,X}. ">>, [], - {errors,[{{2,36},erl_lint,illegal_bin_pattern}, - {{2,36},erl_lint,illegal_bin_pattern}, - {{2,46},erl_lint,illegal_bin_pattern}],[]}}, - {otp_7227_8, + []}, + {otp_7227_8, <<"t(Val) -> (<> = X) = (Y = <>) = Val, {A,B,X,Y}. ">>, [], - {errors,[{{2,40},erl_lint,illegal_bin_pattern}],[]}}, + []}, {otp_7227_9, <<"t(Val) -> (Z = <> = X) = (Y = <> = W) = Val, {A,B,X,Y,Z,W}. ">>, [], - {errors,[{{2,44},erl_lint,illegal_bin_pattern}],[]}} + []} ], [] = run(Config, Ts), ok. +%% GH-6348/OTP-18297: Allow aliases of binary patterns. +binary_aliases(Config) when is_list(Config) -> + Ts = [{binary_aliases_1, + <<"t([<> = <<_:8,Data:Size/bits>>]) -> + Data. + ">>, + [], + {errors,[{{1,55},erl_lint,{unbound_var,'Size'}}],[]}}, + {binary_aliases_2, + <<"t(#{key := <>} = #{key := <<_:8,Data:Size/bits>>}) -> + Data. + ">>, + [], + {errors,[{{1,73},erl_lint,{unbound_var,'Size'}}],[]}}, + {binary_aliases_3, + <<"t(<<_:8,Data:Size/bits>> = <>) -> + Data. + ">>, + [], + {errors,[{{1,34},erl_lint,{unbound_var,'Size'}}],[]}}, + {binary_aliases_4, + <<"t([<<_:8,Data:Size/bits>> = <>]) -> + Data. + ">>, + [], + {errors,[{{1,35},erl_lint,{unbound_var,'Size'}}],[]}}, + {binary_aliases_5, + <<"t(Bin) -> + <<_:8,A:Size>> = <<_:8,B:Size/bits>> = <> = Bin, + {A,B,Size}. + ">>, + [], + []}, + {binary_aliases_6, + <<"t(<<_:8,A:Size>> = <<_:8,B:Size/bits>> = <>) -> + {A,B,Size}. + ">>, + [], + {errors,[{{1,31},erl_lint,{unbound_var,'Size'}}, + {{1,48},erl_lint,{unbound_var,'Size'}}], + []}} + ], + [] = run(Config, Ts), + ok. + %% OTP-5494. Warnings for functions exported more than once. otp_5494(Config) when is_list(Config) -> Ts = [{otp_5494_1, @@ -3876,31 +3924,57 @@ maps_type(Config) when is_list(Config) -> [] = run(Config, Ts), ok. +%% GH-6348/OTP-18297: In OTP 26 parallel matching of maps +%% has been extended. maps_parallel_match(Config) when is_list(Config) -> - Ts = [{parallel_map_patterns_unbound1, + Ts = [{parallel_map_patterns_unbound, <<" t(#{} = M) -> - #{K := V} = #{k := K} = M, + #{k := K} = #{K := V} = M, V. ">>, [], - {errors,[{{3,18},erl_lint,{unbound_var,'K'}}],[]}}, - {parallel_map_patterns_unbound2, + {errors,[{{3,30},erl_lint,{unbound_var,'K'}}],[]}}, + {parallel_map_patterns_not_toplevel1, + <<" + t(#{} = M) -> + [#{K1 := V1} = + #{K2 := V2} = + #{k1 := K1,k2 := K2}] = [M], + [V1,V2]. + ">>, + [], + {errors,[{{3,19},erl_lint,{unbound_var,'K1'}}, + {{4,19},erl_lint,{unbound_var,'K2'}}],[]}}, + {parallel_map_patterns_unbound_not_toplevel2, <<" t(#{} = M) -> + [#{k := K} = #{K := V}] = [M], + V. + ">>, + [], + {errors,[{{3,31},erl_lint,{unbound_var,'K'}}],[]}}, + {parallel_map_patterns_bound1, + <<" + t(#{} = M,K1,K2) -> #{K1 := V1} = #{K2 := V2} = #{k1 := K1,k2 := K2} = M, [V1,V2]. ">>, [], - {errors,[{{3,18},erl_lint,{unbound_var,'K1'}}, - {{3,18},erl_lint,{unbound_var,'K1'}}, - {{4,18},erl_lint,{unbound_var,'K2'}}, - {{4,18},erl_lint,{unbound_var,'K2'}}],[]}}, - {parallel_map_patterns_bound, + []}, + {parallel_map_patterns_bound2, <<" - t(#{} = M,K1,K2) -> + t(#{} = M) -> + #{K := V} = #{k := K} = M, + V. + ">>, + [], + []}, + {parallel_map_patterns_bound3, + <<" + t(#{} = M) -> #{K1 := V1} = #{K2 := V2} = #{k1 := K1,k2 := K2} = M, diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl index 74c8aabf8e4c..f9b0580c8efd 100644 --- a/lib/stdlib/test/qlc_SUITE.erl +++ b/lib/stdlib/test/qlc_SUITE.erl @@ -401,6 +401,7 @@ nomatch(Config) when is_list(Config) -> %% {warnings,[{{3,52},qlc,nomatch_pattern}]}}, {warnings,[{{3,37},v3_core,{nomatch,pattern}}]}}, + %% No longer illegal in OTP 26. {nomatch4, <<"nomatch() -> etsc(fun(E) -> @@ -411,7 +412,7 @@ nomatch(Config) when is_list(Config) -> end, [{<<34>>},{<<40>>}]). ">>, [], - {errors,[{{3,48},erl_lint,illegal_bin_pattern}],[]}}, + []}, {nomatch5, <<"nomatch() -> diff --git a/system/doc/reference_manual/expressions.xml b/system/doc/reference_manual/expressions.xml index b5b920cc7984..1e9c5a4bab5f 100644 --- a/system/doc/reference_manual/expressions.xml +++ b/system/doc/reference_manual/expressions.xml @@ -148,7 +148,7 @@ Name1
 Pattern1 = Pattern2

When matched against a term, both Pattern1 and - Pattern2 are matched against the term. The idea + Pattern2 are matched against the term. The idea behind this feature is to avoid reconstruction of terms.

Example:

@@ -193,20 +193,26 @@ case {Value, Result} of
 
   
Match -

The following matches Expr1, a pattern, against - Expr2:

+

The following matches Pattern, a pattern, against + Expr:

-Expr1 = Expr2
+Pattern = Expr

If the matching succeeds, any unbound variable in the pattern - becomes bound and the value of Expr2 is returned.

+ becomes bound and the value of Expr is returned.

+

If multiple match operators are applied in sequence, they will be + evaluated from right to left.

If the matching fails, a badmatch run-time error occurs.

Examples:

-1> {A, B} = {answer, 42}.
+1> {A, B} = T = {answer, 42}.
 {answer,42}
 2> A.
 answer
-3> {C, D} = [1, 2].
+3> B.
+42
+4> T.
+{answer,42}
+5> {C, D} = [1, 2].
 ** exception error: no match of right-hand side value [1,2]