From 2ffc98ffa00fe6cf6261b6d8a1544983c6cf3282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Thu, 3 Nov 2022 09:33:24 +0100 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 in a clause, 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 which is equivalent to: Var = Expr PatternN = Var . . . Pattern2 = Var Pattern1 = Var Given that there is a well-defined evaluation order from right to left, one would expect 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 Closes #6444 Closes #6467 --- lib/compiler/src/beam_ssa_opt.erl | 27 ++- lib/compiler/src/v3_core.erl | 113 +++++++++++-- lib/compiler/test/bs_match_SUITE.erl | 178 +++++++++++++++++++- lib/compiler/test/map_SUITE.erl | 42 ++++- lib/compiler/test/match_SUITE.erl | 113 +++++++++++-- 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 ++- 10 files changed, 593 insertions(+), 201 deletions(-) diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 11e065e6a6bf..d51cd61d2ded 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -1940,14 +1940,15 @@ coalesce_skips_is(_, _, _) -> %%% Short-cutting binary matching instructions. %%% -ssa_opt_bsm_shortcut({#opt_st{ssa=Linear}=St, FuncDb}) -> - Positions = bsm_positions(Linear, #{}), +ssa_opt_bsm_shortcut({#opt_st{ssa=Linear0}=St, FuncDb}) -> + Positions = bsm_positions(Linear0, #{}), case map_size(Positions) of 0 -> %% No binary matching instructions. {St, FuncDb}; _ -> - {St#opt_st{ssa=bsm_shortcut(Linear, Positions)}, FuncDb} + Linear = bsm_shortcut(Linear0, Positions), + ssa_opt_live({St#opt_st{ssa=Linear}, FuncDb}) end. bsm_positions([{L,#b_blk{is=Is,last=Last}}|Bs], PosMap0) -> @@ -1988,20 +1989,30 @@ bsm_update_bits([_,_,_,#b_literal{val=Sz},#b_literal{val=U}], Bits) Bits + Sz*U; bsm_update_bits(_, Bits) -> Bits. -bsm_shortcut([{L,#b_blk{is=Is,last=Last0}=Blk}|Bs], PosMap) -> +bsm_shortcut([{L,#b_blk{is=Is,last=Last0}=Blk}|Bs], PosMap0) -> case {Is,Last0} of {[#b_set{op=bs_match,dst=New,args=[_,Old|_]}, #b_set{op={succeeded,guard},dst=Bool,args=[New]}], #b_br{bool=Bool,fail=Fail}} -> - case PosMap of - #{Old:=Bits,Fail:={TailBits,NextFail}} when Bits > TailBits -> + case PosMap0 of + #{Old := Bits,Fail := {TailBits,NextFail}} when Bits > TailBits -> Last = Last0#b_br{fail=NextFail}, - [{L,Blk#b_blk{last=Last}}|bsm_shortcut(Bs, PosMap)]; + [{L,Blk#b_blk{last=Last}}|bsm_shortcut(Bs, PosMap0)]; #{} -> + [{L,Blk}|bsm_shortcut(Bs, PosMap0)] + end; + {[#b_set{op=bs_test_tail,dst=Bool,args=[Old,#b_literal{val=TailBits}]}], + #b_br{bool=Bool,succ=Succ}} -> + case PosMap0 of + #{{bs_test_tail,Old,L} := TailBits} -> + Last = beam_ssa:normalize(Last0#b_br{fail=Succ}), + [{L,Blk#b_blk{last=Last}}|bsm_shortcut(Bs, PosMap0)]; + #{} -> + PosMap = PosMap0#{{bs_test_tail,Old,Succ} => TailBits}, [{L,Blk}|bsm_shortcut(Bs, PosMap)] end; {_,_} -> - [{L,Blk}|bsm_shortcut(Bs, PosMap)] + [{L,Blk}|bsm_shortcut(Bs, PosMap0)] end; bsm_shortcut([], _PosMap) -> []. diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index 16b3ac340f79..dbef4fa7e249 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -1050,6 +1050,8 @@ letify_aliases(P, E) -> sanitize({match,L,P1,P2}) -> {tuple,L,[sanitize(P1),sanitize(P2)]}; +sanitize({sequential_match,L,P1,P2}) -> + {tuple,L,[sanitize(P1),sanitize(P2)]}; sanitize({cons,L,H,T}) -> {cons,L,sanitize(H),sanitize(T)}; sanitize({tuple,L,Ps0}) -> @@ -2025,10 +2027,10 @@ 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({match, L, P, E}, E0) -> + fold_match(E, {sequential_match, L, P, E0}); +fold_match(E, E0) -> + {E0, E}. %% pattern(Pattern, State) -> {CorePat,[PreExp],State}. %% Transform a pattern by removing line numbers. We also normalise @@ -2055,9 +2057,56 @@ pattern({bin,L,Ps}, St0) -> {Segments,St} = pat_bin(Ps, St0), {#ibinary{anno=#a{anno=lineno_anno(L, St)},segments=Segments},St}; pattern({match,_,P1,P2}, St) -> + %% Handle aliased patterns in a clause. Example: + %% + %% f({a,b} = {A,B}) -> . . . + %% + %% The `=` operator does not have any defined order in which the + %% two patterns are matched. Therefore, this example can safely be + %% rewritten like so: + %% + %% f({a=A,b=B}) -> . . . + %% + %% Aliased patterns that are illegal, such as: + %% + %% f(#{Key := Value} = {key := Key}) -> . . . + %% + %% have already been rejected by erl_lint. + %% {Cp1,St1} = pattern(P1, St), {Cp2,St2} = pattern(P2, St1), {pat_alias(Cp1, Cp2),St2}; +pattern({sequential_match,_,P1,P2}, St) -> + %% Handle sequential matching in a function body. Example: + %% + %% f(Map) -> + %% #{Key := Value} = {key := Key} = Map, + %% Value. + %% + %% In a function body, the patterns are matched one at a time to + %% the expression, going from right to left, making the example + %% equivalent to: + %% + %% f(Map) -> + %% {key := Key} = Map, + %% #{Key := Value} = Map, + %% Value. + %% + {Cp1,St1} = pattern(P1, St), + {Cp2,St2} = pattern(P2, St1), + + case Cp2 of + #c_cons{anno=[sequential_match]} -> + ok; + _ -> + %% Reject pattern aliases that obviously cannot match. + _ = pat_alias(Cp1, Cp2), + ok + end, + + %% Set up sequential matching of P1 and P2. + P = #c_cons{anno=[sequential_match],hd=Cp1,tl=Cp2}, + {P,St2}; %% Evaluate compile-time expressions. pattern({op,_,'++',{nil,_},R}, St) -> pattern(R, St); @@ -2199,9 +2248,17 @@ pat_alias(P1, #c_var{}=Var) -> 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]}) -> + %% Handle aliases of binary patterns in a clause. Example: + %% f(<> = <>) -> . . . + #ibitstr{anno=#a{anno=Anno}=A} = S0, + S = S0#ibitstr{anno=A#a{anno=[sequential_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 @@ -2803,9 +2860,9 @@ uexpr_list(Les0, Ks, St0) -> %% upattern(Pat, [KnownVar], State) -> %% {Pat,[GuardTest],[NewVar],[UsedVar],State}. -upattern(#c_var{name='_'}, _, St0) -> +upattern(#c_var{anno=Anno,name='_'}, _, St0) -> {New,St1} = new_var_name(St0), - {#c_var{name=New},[],[New],[],St1}; + {#c_var{anno=Anno,name=New},[],[New],[],St1}; upattern(#c_var{name=V}=Var, Ks, St0) -> case is_element(V, known_get(Ks)) of true -> @@ -3008,9 +3065,10 @@ ren_pat(#ibinary{segments=Es0}=P, Ks, {Isub,Osub0}, St0) -> {Es,_Isub,Osub,St} = ren_pat_bin(Es0, Ks, Isub, Osub0, St0), {P#ibinary{segments=Es},{Isub,Osub},St}; ren_pat(P, Ks0, {_,_}=Subs0, St0) -> + Anno = cerl:get_ann(P), Es0 = cerl:data_es(P), {Es,Subs,St} = ren_pats(Es0, Ks0, Subs0, St0), - {cerl:make_data(cerl:data_type(P), Es),Subs,St}. + {cerl:ann_make_data(Anno, cerl:data_type(P), Es),Subs,St}. ren_pat_bin([#ibitstr{val=Val0,size=Sz0}=E|Es0], Ks, Isub0, Osub0, St0) -> Sz = ren_get_subst(Sz0, Isub0), @@ -3635,9 +3693,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}; + {sequential_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, []); @@ -3652,6 +3715,12 @@ split_pat(#c_alias{pat=Pat}=Alias0, St0) -> Alias = Alias0#c_alias{pat=Var}, {Alias,{split,[Var],Ps,Split},St} end; +split_pat(#c_cons{anno=[sequential_match],hd=Cons1,tl=Cons2}, St0) -> + %% Handle sequential matching of all types of patterns. + {Var,St} = new_var(St0), + BefCons = #c_alias{var=Var,pat=Cons1}, + Wrap = fun(Body) -> Body end, + {BefCons,{split,[Var],Wrap,Cons2,nil},St}; split_pat(Data, St0) -> Type = cerl:data_type(Data), Es = cerl:data_es(Data), @@ -3719,7 +3788,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(sequential_match, Anno0) of + true -> + Anno = Anno0 -- [sequential_match], + S = S0#c_bitstr{anno=Anno}, + {sequential_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 +3817,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 +3829,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 7fe6d89883dc..8c0db27828fb 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -50,7 +50,8 @@ combine_empty_segments/1,hangs_forever/1, bs_saved_position_units/1,empty_matches/1, trim_bs_start_match_resume/1, - gh_6410/1]). + gh_6410/1, + binary_aliases/1]). -export([coverage_id/1,coverage_external_ignore/2]). @@ -91,7 +92,8 @@ groups() -> many_clauses,combine_empty_segments,hangs_forever, bs_saved_position_units,empty_matches, trim_bs_start_match_resume, - gh_6410]}]. + gh_6410, + binary_aliases]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -2670,6 +2672,178 @@ do_gh_6410(X) -> X end). +%% 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>>)), + + F14 = fun(Bin) -> + [<<_:Y>> | _] = [_ | Y] = id(Bin), + ok + end, + ok = F14([<<>>|0]), + ok = F14([<<-1:32>>|32]), + {'EXIT',{{badmatch,[<<0:16>>|0]},_}} = catch F14([<<0:16>>|0]), + {'EXIT',{{badmatch,[<<0:16>>|atom]},_}} = catch F14([<<0:16>>|atom]), + + F15 = fun(Bin) -> + {<<_:Y>>, _} = {_, Y} = id(Bin), + Y + end, + 0 = F15({<<>>, 0}), + 32 = F15({<<-1:32>>, 32}), + {'EXIT',{{badmatch,{<<0:16>>,0}},_}} = catch F15({<<0:16>>, 0}), + {'EXIT',{{badmatch,{<<0:16>>,atom}},_}} = catch F15({<<0:16>>, atom}), + + F16 = fun(Bin) -> + [{<<_:Y>>, _}] = [{_, Y}] = id(Bin), + Y + end, + 0 = F16([{<<>>, 0}]), + 32 = F16([{<<-1:32>>, 32}]), + {'EXIT',{{badmatch,[{<<0:16>>,0}]},_}} = catch F16([{<<0:16>>, 0}]), + {'EXIT',{{badmatch,[{<<0:16>>,atom}]},_}} = catch F16([{<<0:16>>, atom}]), + + F17 = fun(#{[] := <<_>>, [] := <<_>>}) -> ok end, + ok = F17(id(#{[] => <<42>>})), + {'EXIT',{function_clause,_}} = catch F17(id(#{[] => <<>>})), + {'EXIT',{function_clause,_}} = catch F17(id(atom)), + + F18 = fun(<<_>> = Bin) -> + case Bin of + <<_>> -> ok; + _ -> error + end; + (_) -> error + end, + ok = F18(id(<<42>>)), + error = F18(<<>>), + error = F18(<<1:1>>), + error = F18(atom), + + a = gh_6467(id(0), id(<<0>>), id(0)), + {'EXIT',{{badmatch,0},_}} = catch gh_6467(id(0), id(<<0>>), id([])), + {'EXIT',{{badmatch,<<7>>},_}} = catch gh_6467(id(<<7>>), id(<<33>>), id([])), + + true = gh6415_a(<<42>>, true), + error = gh6415_a(<<42>>, false), + error = gh6415_a(<<>>, false), + error = gh6415_a(<<>>, not_bool), + error = gh6415_a(any, true), + error = gh6415_a(any, false), + + ok = gh6415_b(true, <<99>>), + ok = gh6415_b(false, <<99>>), + error = gh6415_b(true, <<>>), + error = gh6415_b(false, <<>>), + + ok = gh6415_c(<<10>>, true), + error = gh6415_c(<<10>>, false), + error = gh6415_c(<<>>, true), + error = gh6415_c(42, true), + + ok. + +%% GH-6467. When a matched out value was never used, the bs_match instruction +%% was not rewritten to a bs_skip instruction, causing an assertion fail in +%% beam_ssa_pre_codegen. +gh_6467(X, _, []) -> + [0 || _ <- (<<_:Y>> = (Y = ((_ = X) = X)))]; +gh_6467(_, <>, _) -> + a. + +gh6415_a(<>, Y) when (<> == false orelse (Y andalso true)); Y -> + Y; +gh6415_a(_, _) -> + error. + +gh6415_b(X, <>) when ((Y > X) xor X); not X; X -> + ok; +gh6415_b(_, _) -> + error. + +gh6415_c(<>, Y) when {(X / 1), (Y andalso true)}; Y -> + ok; +gh6415_c(_, _) -> + error. + %%% Utilities. + id(I) -> I. diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl index 8716d85477c0..5c8de7c6dca4 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,42 @@ 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(id(#{k => {a,key}, key => value})), + + F2 = fun(#{} = #{}) -> ok end, + ok = F2(id(#{})), + ok = F2(id(#{key => whatever})), + + F3 = fun(#{a := V} = #{}) -> V end, + {a,b,c} = F3(id(#{a => {a,b,c}})), + + F4 = fun(Map) -> + [#{Key := Value} | _] = [_ | Key] = id(Map), + Value + end, + bar = F4([#{foo => bar} | foo]), + + F5 = fun(Map) -> + {#{Key := Value}, _} = {_, Key} = id(Map), + Value + end, + light = F5({#{frotz => light}, frotz}), + + F6 = fun(E) -> + #{Y := _} = (Y = ((_ = X) = E)) + end, + {'EXIT',{{badmatch,0},_}} = catch F6(id(0)), + {'EXIT',{{badmatch,#{}},_}} = catch F6(id(#{})), + {'EXIT',{{badmatch,#{key := value}},_}} = catch F6(id(#{key => value})), + + ok. + %% aux rand_terms(0) -> []; diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl index 5fc487e7a9fd..c2648d090f38 100644 --- a/lib/compiler/test/match_SUITE.erl +++ b/lib/compiler/test/match_SUITE.erl @@ -151,14 +151,19 @@ aliases(Config) when is_list(Config) -> 6 = tup_lit_alias({1,2,3}), 6 = tup_lit_alias_rev({1,2,3}), - {42,42,42,42} = multiple_aliases_1(42), - {7,7,7} = multiple_aliases_2(7), - {{a,b},{a,b},{a,b}} = multiple_aliases_3({a,b}), + {1,2,3,4} = list_in_tuple({container, [1,2,3], 4}), + {a,b,c,d} = list_in_tuple({container, [a,b,c], d}), + + {13,y,13,17,x,{y,13},17} = tuple_in_tuple({x, {y,13}, 17}), + {a,y,a,b,x,{y,a},b} = tuple_in_tuple({x, {y,a}, b}), + + {42,42,42,42} = multiple_aliases_1(id(42)), + {7,7,7} = multiple_aliases_2(id(7)), + {{a,b},{a,b},{a,b}} = multiple_aliases_3(id({a,b})), + {[x,y,z],[x,y,z],[x,y,z]} = multiple_aliases_4(id([x,y,z])), %% Lists/literals. - {a,b} = list_alias1([a,b]), - {a,b} = list_alias2([a,b]), - {a,b} = list_alias3([a,b]), + {a,b} = list_alias(id([a,b])), %% Multiple matches. {'EXIT',{{badmatch,home},_}} = @@ -259,9 +264,20 @@ three_2(A= C) -> {A,B,C}. -tuple_alias({A,B,C}={X,Y,Z}) -> +tuple_alias(Expr) -> + Res = tuple_alias_a(Expr), + Res = tuple_alias_b(Expr). + +tuple_alias_a({A,B,C} = {X,Y,Z}) -> + {A,B,C,X,Y,Z}; +tuple_alias_a({A,B} = {C,D} = {E,F}) -> + {A,B,C,D,E,F}. + +tuple_alias_b({_,_,_}=Expr) -> + {A,B,C} = {X,Y,Z} = Expr, {A,B,C,X,Y,Z}; -tuple_alias({A,B}={C,D}={E,F}) -> +tuple_alias_b({_,_}=Expr) -> + {A,B} = {C,D} = {E,F} = Expr, {A,B,C,D,E,F}. tup_lit_alias({A,B,C}={1,2,3}) -> @@ -270,22 +286,91 @@ tup_lit_alias({A,B,C}={1,2,3}) -> tup_lit_alias_rev({1,2,3}={A,B,C}) -> A+B+C. -multiple_aliases_1((A=B)=(C=D)) -> +list_in_tuple(E) -> + Res = list_in_tuple_a(E), + Res = list_in_tuple_b(E). + +list_in_tuple_a({container, [_,_,_] = [A,B,C], D}) -> + {A,B,C,D}. + +list_in_tuple_b(E) -> + {container, [_,_,_] = [A,B,C], D} = E, + {A,B,C,D}. + +tuple_in_tuple(Expr) -> + Res = tuple_in_tuple_a(Expr), + Res = tuple_in_tuple_b(Expr). + +tuple_in_tuple_a({x, {y,A} = {B,C}, D} = {E, F, G}) -> + {A,B,C,D,E,F,G}. + +tuple_in_tuple_b(Expr) -> + {x, {y,A} = {B,C}, D} = {E, F, G} = Expr, + {A,B,C,D,E,F,G}. + +multiple_aliases_1(Expr) -> + Res = multiple_aliases_1a(Expr), + Res = multiple_aliases_1b(Expr). + +multiple_aliases_1a((A=B) = (C=D)) -> + {A,B,C,D}. + +multiple_aliases_1b(Expr) -> + (A=B) = (C=D) = Expr, {A,B,C,D}. -multiple_aliases_2((A=B)=(A=C)) -> +multiple_aliases_2((A=B) = (A=C)) -> + {A,B,C}. + +multiple_aliases_3(Expr) -> + Res = multiple_aliases_3a(Expr), + Res = multiple_aliases_3b(Expr). + +multiple_aliases_3a((A={_,_}=B)={_,_}=C) -> + {A,B,C}. + +multiple_aliases_3b(Expr) -> + (A={_,_}=B) = {_,_} = C = Expr, + {A,B,C}. + +multiple_aliases_4(Expr) -> + Res = multiple_aliases_4a(Expr), + Res = multiple_aliases_4b(Expr). + +multiple_aliases_4a((A=[_,_,_]=B) = [_,_,_] = C) -> {A,B,C}. -multiple_aliases_3((A={_,_}=B)={_,_}=C) -> +multiple_aliases_4b(Expr) -> + (A=[_,_,_]=B) = [_,_,_] = C = Expr, {A,B,C}. -list_alias1([a,b]=[X,Y]) -> +list_alias(Expr) -> + Res = list_alias1a(Expr), + Res = list_alias1b(Expr), + Res = list_alias2a(Expr), + Res = list_alias2b(Expr), + Res = list_alias3a(Expr), + Res = list_alias3b(Expr). + +list_alias1a([a,b]=[X,Y]) -> + {X,Y}. + +list_alias1b(Expr) -> + [a,b] = [X,Y] = Expr, + {X,Y}. + +list_alias2a([X,Y]=[a,b]) -> + {X,Y}. + +list_alias2b(Expr) -> + [X,Y] = [a,b] = Expr, {X,Y}. -list_alias2([X,Y]=[a,b]) -> +list_alias3a([X,b]=[a,Y]) -> {X,Y}. -list_alias3([X,b]=[a,Y]) -> +list_alias3b(Expr) -> + [X,b] = [a,Y]= Expr, {X,Y}. non_matching_aliases(_Config) -> 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..45ca4b2092e7 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 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]