From 6a9ebe62aadd241424d9f15998e7ac488b20ac6b 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_codegen.erl | 17 +- lib/compiler/src/beam_ssa_opt.erl | 95 ++++-- lib/compiler/src/v3_core.erl | 286 ++++++++++------ lib/compiler/test/bs_match_SUITE.erl | 360 +++++++++++++++++++- lib/compiler/test/map_SUITE.erl | 42 ++- lib/compiler/test/match_SUITE.erl | 139 +++++++- lib/compiler/test/warnings_SUITE.erl | 3 +- 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 | 138 +++++++- 12 files changed, 1060 insertions(+), 321 deletions(-) diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl index e4aa2e5568c3..869490e71033 100644 --- a/lib/compiler/src/beam_ssa_codegen.erl +++ b/lib/compiler/src/beam_ssa_codegen.erl @@ -2198,7 +2198,8 @@ bs_translate([I|Is0]) -> [I|bs_translate(Is0)]; {Ctx,Fail0,First} -> {Instrs0,Fail,Is} = bs_translate_collect(Is0, Ctx, Fail0, [First]), - Instrs = bs_eq_fixup(Instrs0), + Instrs1 = bs_seq_match_fixup(Instrs0), + Instrs = bs_eq_fixup(Instrs1), [{bs_match,Fail,Ctx,{commands,Instrs}}|bs_translate(Is)] end; bs_translate([]) -> []. @@ -2224,6 +2225,20 @@ bs_translate_fixup([{test_tail,Bits}|Is0]) -> bs_translate_fixup(Is) -> reverse(Is). +%% Fix up matching of multiple binaries in parallel. Example: +%% f(<<_:8>> = <>) -> ... +bs_seq_match_fixup([{test_tail,Bits},{ensure_exactly,Bits}|Is]) -> + [{ensure_exactly,Bits}|bs_seq_match_fixup(Is)]; +bs_seq_match_fixup([{test_tail,Bits0},{ensure_at_least,Bits1,Unit}|Is]) + when Bits0 >= Bits1, Bits0 rem Unit =:= 0 -> + %% The tail test is at least as strict as the ensure_at_least test. + [{ensure_exactly,Bits0}|bs_seq_match_fixup(Is)]; +bs_seq_match_fixup([{test_tail,Bits}|Is]) -> + [{ensure_exactly,Bits}|bs_seq_match_fixup(Is)]; +bs_seq_match_fixup([I|Is]) -> + [I|bs_seq_match_fixup(Is)]; +bs_seq_match_fixup([]) -> []. + bs_eq_fixup([{'=:=',nil,Bits,Value}|Is]) -> EqInstrs = bs_eq_fixup_split(Bits, <>), EqInstrs ++ bs_eq_fixup(Is); diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 11e065e6a6bf..fecb5bebd5a8 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -1834,15 +1834,17 @@ trim_try_is([], _Killed) -> %%% with bs_test_tail. %%% -ssa_opt_bsm({#opt_st{ssa=Linear}=St, FuncDb}) -> - Extracted0 = bsm_extracted(Linear), +ssa_opt_bsm({#opt_st{ssa=Linear0}=St, FuncDb}) -> + Extracted0 = bsm_extracted(Linear0), Extracted = sets:from_list(Extracted0, [{version, 2}]), - {St#opt_st{ssa=bsm_skip(Linear, Extracted)}, FuncDb}. + Linear1 = bsm_skip(Linear0, Extracted), + Linear = bsm_coalesce_skips(Linear1, #{}), + {St#opt_st{ssa=Linear}, FuncDb}. bsm_skip([{L,#b_blk{is=Is0}=Blk}|Bs0], Extracted) -> Bs = bsm_skip(Bs0, Extracted), Is = bsm_skip_is(Is0, Extracted), - coalesce_skips({L,Blk#b_blk{is=Is}}, Bs); + [{L,Blk#b_blk{is=Is}}|Bs]; bsm_skip([], _) -> []. bsm_skip_is([I0|Is], Extracted) -> @@ -1878,22 +1880,31 @@ bsm_extracted([{_,#b_blk{is=Is}}|Bs]) -> end; bsm_extracted([]) -> []. +bsm_coalesce_skips([{L,Blk0}|Bs0], Renames0) -> + case coalesce_skips({L,Blk0}, Bs0, Renames0) of + not_possible -> + [{L,Blk0}|bsm_coalesce_skips(Bs0, Renames0)]; + {Bs,Renames} -> + bsm_coalesce_skips(Bs, Renames) + end; +bsm_coalesce_skips([], _Renames) -> []. + coalesce_skips({L,#b_blk{is=[#b_set{op=bs_extract}=Extract|Is0], - last=Last0}=Blk0}, Bs0) -> - case coalesce_skips_is(Is0, Last0, Bs0) of + last=Last0}=Blk0}, Bs0, Renames0) -> + case coalesce_skips_is(Is0, Last0, Bs0, Renames0) of not_possible -> - [{L,Blk0}|Bs0]; - {Is,Last,Bs} -> + not_possible; + {Is,Last,Bs,Renames} -> Blk = Blk0#b_blk{is=[Extract|Is],last=Last}, - [{L,Blk}|Bs] + {[{L,Blk}|Bs],Renames} end; -coalesce_skips({L,#b_blk{is=Is0,last=Last0}=Blk0}, Bs0) -> - case coalesce_skips_is(Is0, Last0, Bs0) of +coalesce_skips({L,#b_blk{is=Is0,last=Last0}=Blk0}, Bs0, Renames0) -> + case coalesce_skips_is(Is0, Last0, Bs0, Renames0) of not_possible -> - [{L,Blk0}|Bs0]; - {Is,Last,Bs} -> + not_possible; + {Is,Last,Bs,Renames} -> Blk = Blk0#b_blk{is=Is,last=Last}, - [{L,Blk}|Bs] + {[{L,Blk}|Bs],Renames} end. coalesce_skips_is([#b_set{op=bs_match, @@ -1901,53 +1912,59 @@ coalesce_skips_is([#b_set{op=bs_match, Ctx0,Type,Flags, #b_literal{val=Size0}, #b_literal{val=Unit0}], - dst=Ctx}=Skip0, + dst=PrevCtx}=Skip0, #b_set{op={succeeded,guard}}], #b_br{succ=L2,fail=Fail}=Br0, - Bs0) when is_integer(Size0) -> + Bs0, + Renames0) when is_integer(Size0) -> case Bs0 of [{L2,#b_blk{is=[#b_set{op=bs_match, dst=SkipDst, - args=[#b_literal{val=skip},Ctx,_,_, + args=[#b_literal{val=skip},PrevCtx,_,_, #b_literal{val=Size1}, #b_literal{val=Unit1}]}, #b_set{op={succeeded,guard}}=Succeeded], last=#b_br{fail=Fail}=Br}}|Bs] when is_integer(Size1) -> + OldCtx = maps:get(Ctx0, Renames0, Ctx0), SkipBits = Size0 * Unit0 + Size1 * Unit1, Skip = Skip0#b_set{dst=SkipDst, - args=[#b_literal{val=skip},Ctx0, + args=[#b_literal{val=skip},OldCtx, Type,Flags, #b_literal{val=SkipBits}, #b_literal{val=1}]}, Is = [Skip,Succeeded], - {Is,Br,Bs}; + Renames = Renames0#{PrevCtx => Ctx0}, + {Is,Br,Bs,Renames}; [{L2,#b_blk{is=[#b_set{op=bs_test_tail, - args=[Ctx,#b_literal{val=TailSkip}]}], + args=[PrevCtx,#b_literal{val=TailSkip}]}], last=#b_br{succ=NextSucc,fail=Fail}}}|Bs] -> + OldCtx = maps:get(Ctx0, Renames0, Ctx0), SkipBits = Size0 * Unit0, TestTail = Skip0#b_set{op=bs_test_tail, - args=[Ctx0,#b_literal{val=SkipBits+TailSkip}]}, + args=[OldCtx,#b_literal{val=SkipBits+TailSkip}]}, Br = Br0#b_br{bool=TestTail#b_set.dst,succ=NextSucc}, Is = [TestTail], - {Is,Br,Bs}; + Renames = Renames0#{PrevCtx => Ctx0}, + {Is,Br,Bs,Renames}; _ -> not_possible end; -coalesce_skips_is(_, _, _) -> +coalesce_skips_is(_, _, _, _) -> not_possible. %%% %%% 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 +2005,36 @@ 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,fail=Fail}} -> + case PosMap0 of + #{{bs_test_tail,Old,L} := ActualTailBits} -> + Last1 = if + TailBits =:= ActualTailBits -> + Last0#b_br{fail=Succ}; + true -> + Last0#b_br{succ=Fail} + end, + Last = beam_ssa:normalize(Last1), + [{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 e4a6550b018b..be7c8e359090 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -81,8 +81,8 @@ -export([module/2,format_error/1]). --import(lists, [reverse/1,reverse/2,map/2,member/2,foldl/3,foldr/3,mapfoldl/3, - splitwith/2,keyfind/3,sort/1,droplast/1,last/1, +-import(lists, [any/2,reverse/1,reverse/2,map/2,member/2,foldl/3,foldr/3,mapfoldl/3, + splitwith/2,keydelete/3,keyfind/3,keymember/3,sort/1,droplast/1,last/1, duplicate/2]). -import(ordsets, [add_element/2,del_element/2,is_element/2, union/1,union/2,intersection/2,subtract/2]). @@ -896,85 +896,47 @@ expr({call,L,FunExp,As0}, St0) -> Lanno = lineno_anno(L, St2), {#iapply{anno=#a{anno=Lanno},op=Fun,args=As1},Fps ++ Aps,St2}; expr({match,L,P0,E0}, St0) -> - %% First fold matches together to create aliases. - {P1,E1} = fold_match(E0, P0), - St1 = set_wanted(P1, St0), - {E2,Eps1,St2} = novars(E1, St1), - St3 = St2#core{wanted=St0#core.wanted}, - {P2,St4} = try - pattern(P1, St3) - catch - throw:Thrown -> - {Thrown,St3} - end, - {Fpat,St5} = new_var(St4), - Lanno = lineno_anno(L, St5), - Fc = fail_clause([Fpat], Lanno, c_tuple([#c_literal{val=badmatch},Fpat])), - case P2 of - nomatch -> - %% The pattern will not match. We must take care here to - %% bind all variables that the pattern would have bound - %% so that subsequent expressions do not refer to unbound - %% variables. - %% - %% As an example, this code: - %% - %% [X] = {Y} = E, - %% X + Y. - %% - %% will be rewritten to: - %% - %% error({badmatch,E}), - %% case E of - %% {[X],{Y}} -> - %% X + Y; - %% Other -> - %% error({badmatch,Other}) - %% end. - %% - St6 = add_warning(L, {nomatch,pattern}, St5), - {Expr,Eps3,St7} = safe(E1, St6), - SanPat0 = sanitize(P1), - {SanPat,St} = pattern(SanPat0, St7), - Badmatch = c_tuple([#c_literal{val=badmatch},Expr]), - Fail = #iprimop{anno=#a{anno=Lanno}, - name=#c_literal{val=match_fail}, - args=[Badmatch]}, - Eps = Eps3 ++ [Fail], - {#imatch{anno=#a{anno=Lanno},pat=SanPat,arg=Expr,fc=Fc},Eps,St}; - Other when not is_atom(Other) -> - %% We must rewrite top-level aliases to lets to avoid unbound - %% variables in code such as: + St1 = set_wanted(P0, St0), + case fold_match(E0, P0) of + {{sequential_match,_,_,_}=P1,E1} -> + %% Matching of an expression to more than one pattern. Example: %% - %% <<42:Sz>> = Sz = B + %% #{Key := Value} = #{key := Key} = Expr + {E2,Eps1,St2} = safe(E1, St1), + St3 = St2#core{wanted=St0#core.wanted}, + + %% If necessary, bind the expression to a variable to ensure it is + %% only evaluted once. + {Var,Eps2,St4} = + case E2 of + #c_var{} -> + {E2,[],St3}; + _ -> + {Var0,StInt} = new_var(St3), + {Var0,[#iset{var=Var0,arg=E2}],StInt} + end, + + %% Rewrite to a begin/end block matching one pattern at the time + %% (using the `single_match` operator). Example: %% - %% If we would keep the top-level aliases the example would - %% be translated like this: - %% - %% case B of - %% (Sz,1,'integer',['unsigned'|['big']])}#> - %% when 'true' -> - %% . - %% . - %% . - %% - %% Here the variable Sz would be unbound in the binary pattern. - %% - %% Instead we bind Sz in a let to ensure it is bound when - %% used in the binary pattern: - %% - %% let = B - %% in case Sz of - %% <#{#<42>(Sz,1,'integer',['unsigned'|['big']])}#> - %% when 'true' -> - %% . - %% . - %% . - %% - {P3,E3,Eps2} = letify_aliases(P2, E2), - Eps = Eps1 ++ Eps2, - {#imatch{anno=#a{anno=Lanno},pat=P3,arg=E3,fc=Fc},Eps,St5} + %% begin + %% V = Expr, + %% #{key := Key} = V, + %% #{Key := Value} = V + %% end + Block = blockify(L, P1, Var), + {E3,Eps3,St5} = expr({block,L,Block}, St4), + {E3,Eps1 ++ Eps2 ++ Eps3,St5}; + {P0,E1} -> + %% Matching of an expression to a single pattern. Example: + %% {A,B} = Expr + {E2,Eps1,St2} = novars(E1, St1), + St3 = St2#core{wanted=St0#core.wanted}, + {E3,Eps2,St4} = single_match(L, P0, E2, St3), + {E3,Eps1 ++ Eps2,St4} end; +expr({single_match,L,P,#c_var{}=E}, St0) -> + single_match(L, P, E, St0); expr({op,_,'++',{lc,Llc,E,Qs0},More}, St0) -> %% Optimise '++' here because of the list comprehension algorithm. %% @@ -1015,6 +977,56 @@ expr({op,L,Op,L0,R0}, St0) -> module=#c_literal{anno=LineAnno,val=erlang}, name=#c_literal{anno=LineAnno,val=Op},args=As},Aps,St1}. +blockify(L0, {sequential_match,_L1,First,Then}, E) -> + [{single_match,L0,First,E}|blockify(L0, Then, E)]; +blockify(L, P, E) -> + [{single_match,L,P,E}]. + +%% single_match(Line, AbstractPattern, CoreExpr, State0) -> {Expr,Pre,State}. +%% Generate the code for matching an expression against a single pattern. +single_match(L, P0, E, St0) -> + {Fpat,St1} = new_var(St0), + Lanno = lineno_anno(L, St1), + Fc = fail_clause([Fpat], Lanno, c_tuple([#c_literal{val=badmatch},Fpat])), + try pattern(P0, St1) of + {P1,St2} -> + St3 = set_wanted(P0, St2), + St4 = St3#core{wanted=St0#core.wanted}, + {#imatch{anno=#a{anno=Lanno},pat=P1,arg=E,fc=Fc},[],St4} + catch + throw:nomatch -> + %% The pattern will not match. We must take care here to + %% bind all variables that the pattern would have bound + %% so that subsequent expressions do not refer to unbound + %% variables. + %% + %% As an example, this code: + %% + %% ([X] = {Y}) = E, + %% X + Y. + %% + %% will be rewritten to: + %% + %% error({badmatch,E}), + %% case E of + %% {[X],{Y}} -> + %% X + Y; + %% Other -> + %% error({badmatch,Other}) + %% end. + %% + St2 = add_warning(L, {nomatch,pattern}, St1), + {Expr,Eps0,St3} = force_safe(E, St2), + SanPat0 = sanitize(P0), + {SanPat,St} = pattern(SanPat0, St3), + Badmatch = c_tuple([#c_literal{val=badmatch},Expr]), + Fail = #iprimop{anno=#a{anno=Lanno}, + name=#c_literal{val=match_fail}, + args=[Badmatch]}, + Eps = Eps0 ++ [Fail], + {#imatch{anno=#a{anno=Lanno},pat=SanPat,arg=Expr,fc=Fc},Eps,St} + end. + %% set_wanted(Pattern, St) -> St'. %% Suppress warnings for expressions that are bound to the '_' %% variable and variables that begin with '_'. @@ -1029,12 +1041,6 @@ set_wanted({var,_,Var}, St) -> end; set_wanted(_, St) -> St. -letify_aliases(#c_alias{var=V,pat=P0}, E0) -> - {P1,E1,Eps0} = letify_aliases(P0, V), - {P1,E1,[#iset{var=V,arg=E0}|Eps0]}; -letify_aliases(P, E) -> - {P,E,[]}. - %% sanitize(Pat) -> SanitizedPattern %% Rewrite Pat so that it will be accepted by pattern/2 and will %% bind the same variables as the original pattern. @@ -2025,10 +2031,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,6 +2061,22 @@ 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}; @@ -2199,9 +2221,22 @@ 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(#ibinary{segments=[S0|Segs1]}=P, #ibinary{segments=[]}) -> + %% Example: f(<<_:0>> == <>>) -> . . . + #ibitstr{anno=#a{anno=Anno}=A} = S0, + S = S0#ibitstr{anno=A#a{anno=[sequential_match|Anno]}}, + P#ibinary{segments=[S|Segs1]}; + 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 @@ -2814,9 +2849,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 -> @@ -3019,9 +3054,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), @@ -3641,14 +3677,26 @@ split_pats([P0|Ps0], St0) -> split_pats([], _) -> none. -split_pat(#c_binary{segments=Segs0}=Bin, St0) -> +split_pat(#c_binary{anno=Anno0,segments=Segs0}=Bin, St0) -> Vars = gb_sets:empty(), case split_bin_segments(Segs0, Vars, St0, []) of none -> none; - {TailVar,Wrap,Bef,Aft,St} -> - BefBin = Bin#c_binary{segments=Bef}, - {BefBin,{split,[TailVar],Wrap,Bin#c_binary{segments=Aft},nil},St} + {size_var,TailVar,Wrap,Bef,Aft,St1} -> + {BefBin,Anno,St} = size_var_before_bin(Bin, Bef, St1), + {BefBin,{split,[TailVar],Wrap,Bin#c_binary{anno=Anno,segments=Aft},nil},St}; + {sequential_match,Bef,Aft,St1} -> + Anno = keydelete(binary_var, 1, Anno0), + {BefBin,St} = + case keyfind(binary_var, 1, Anno0) of + false -> + {BinVar,StInt} = new_var(St1), + {#c_alias{var=BinVar,pat=Bin#c_binary{segments=Bef}},StInt}; + {binary_var,BinVar} -> + {Bin#c_binary{anno=Anno,segments=Bef},St1} + end, + Wrap = fun(Body) -> Body end, + {BefBin,{split,[BinVar],Wrap,Bin#c_binary{anno=Anno,segments=Aft},nil},St} end; split_pat(#c_map{es=Es}=Map, St) -> split_map_pat(Es, Map, St, []); @@ -3668,6 +3716,28 @@ split_pat(Data, St0) -> Es = cerl:data_es(Data), split_data(Es, Type, St0, []). +size_var_before_bin(#c_binary{anno=Anno0,segments=Segments}=Bin0, Bef, St0) -> + case any(fun(#c_bitstr{anno=Anno}) -> + member(sequential_match, Anno) + end, Segments) of + true -> + case keymember(binary_var, 1, Anno0) of + false -> + {BinVar,St1} = new_var(St0), + Bin = Bin0#c_binary{segments=Bef}, + P = #c_alias{var=BinVar,pat=Bin}, + Anno = [{binary_var,BinVar}|Anno0], + {P,Anno,St1}; + true -> + Anno = keydelete(binary_var, 1, Anno0), + Bin = Bin0#c_binary{anno=Anno,segments=Bef}, + {Bin,Anno0,St0} + end; + false -> + Bin = Bin0#c_binary{segments=Bef}, + {Bin,Anno0,St0} + end. + split_map_pat([#c_map_pair{key=Key,val=Val}=E0|Es], Map0, St0, Acc) -> case eval_map_key(Key, E0, Es, Map0, St0) of none -> @@ -3730,7 +3800,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 @@ -3747,7 +3829,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; @@ -3759,10 +3841,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 ab3b9d002378..3ce1810f39f1 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,bs_match/1]). + gh_6410/1,bs_match/1, + binary_aliases/1]). -export([coverage_id/1,coverage_external_ignore/2]). @@ -91,7 +92,7 @@ groups() -> many_clauses,combine_empty_segments,hangs_forever, bs_saved_position_units,empty_matches, trim_bs_start_match_resume, - gh_6410,bs_match]}]. + gh_6410,bs_match,binary_aliases]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -2685,6 +2686,359 @@ do_bs_match_1(_, X) -> end, X. +%% 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), + + F19 = fun(B) -> + <<42:Sz>> = Sz = <<_>> = B + end, + {'EXIT',{{badmatch,<<0>>},_}} = catch F19(<<0>>), + {'EXIT',{{badmatch,<<>>},_}} = catch F19(<<>>), + {'EXIT',{{badmatch,0},_}} = catch F19(0), + + F20 = fun([<<>>] = [<<>>]) -> ok end, + ok = F20([<<>>]), + + 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([])), + + F21 = fun(<<_:(true andalso 0)>> = <<>>) -> ok; + (_) -> error + end, + ok = F21(<<>>), + error = F21(<<42>>), + error = F21(42), + + 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), + + gh6415_case_clause(42), + gh6415_case_clause(<<42>>), + gh6415_case_clause(a), + + error = gh6415_nomatch(<<>>), + error = gh6415_nomatch(<<42>>), + error = gh6415_nomatch(<<97,98>>), + error = gh6415_nomatch(#{0 => <<>>}), + error = gh6415_nomatch(#{0 => <<42>>}), + error = gh6415_nomatch(#{0 => 42}), + error = gh6415_nomatch(#{}), + error = gh6415_nomatch({a,tuple}), + error = gh6415_nomatch(an_atom), + + 42 = gh6415_match_a(id(<<42>>)), + {'EXIT',{function_clause,_}} = catch gh6415_match_a(id(<<>>)), + {'EXIT',{function_clause,_}} = catch gh6415_match_a(id(a)), + + {42,<<>>} = gh6415_match_b(id(<<42>>)), + {'EXIT',{function_clause,_}} = catch gh6415_match_b(id(<<1,2>>)), + {'EXIT',{function_clause,_}} = catch gh6415_match_b(id(<<>>)), + {'EXIT',{function_clause,_}} = catch gh6415_match_b(id(a)), + + {'EXIT',{_,_}} = catch gh6415_match_c(), + + ok = gh6415_match_d(id(<<163>>)), + {'EXIT',{function_clause,_}} = catch gh6415_match_d(id(<<>>)), + {'EXIT',{function_clause,_}} = catch gh6415_match_d(id(a)), + + ok = gh6415_match_e(id(<<163,0>>)), + ok = gh6415_match_e(id(<<99,8,42>>)), + ok = gh6415_match_e(id(<<99,17,-1:17>>)), + {'EXIT',{function_clause,_}} = catch gh6415_match_e(id(<<163>>)), + {'EXIT',{function_clause,_}} = catch gh6415_match_e(id(<<>>)), + {'EXIT',{function_clause,_}} = catch gh6415_match_e(id(a)), + + 7777 = gh6415_match_f(id(<<17,7777:17>>)), + 1234 = gh6415_match_f(id(<<17,1234:17>>)), + error = gh6415_match_f(id(<<0>>)), + error = gh6415_match_f(id(<<8,42>>)), + + 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. + +gh6415_case_clause(X) -> + {'EXIT',{{case_clause,0},_}} = catch gh6415_case_clause_a(X), + {'EXIT',{{case_clause,0},_}} = catch gh6415_case_clause_b(X), + {'EXIT',{{case_clause,0},_}} = catch gh6415_case_clause_c(X), + {'EXIT',{{case_clause,0},_}} = catch gh6415_case_clause_d(X), + ok. + +gh6415_case_clause_a(X) -> + case 0 of + <<_:(X bor X)>> -> + <<_:Y>> = Y = X + end, + Y. + +gh6415_case_clause_b(X) -> + case 0 of + <<_:(X bor X)>> -> + <<_:Y>> = <> = X + end, + Y. + +gh6415_case_clause_c(X) -> + case 0 of + <<_:(X bor X)>> -> + <<_:Y>> = <> = X + 1 + end, + Y. + +gh6415_case_clause_d(X) -> + case 0 of + <<_:(X bor X)>> -> + <<_:Y>> = <> = [X] + end, + Y. + +gh6415_nomatch(E0) -> + E = id(E0), + Res = gh6415_nomatch_a(E), + Res = gh6415_nomatch_b(E), + Res = gh6415_nomatch_c(E), + Res = gh6415_nomatch_d(E), + Res = gh6415_nomatch_e(E), + Res = gh6415_nomatch_f(E), + Res = gh6415_nomatch_g(E), + Res = gh6415_nomatch_h(E), + Res = gh6415_nomatch_i(E), + Res = gh6415_nomatch_j(E), + Res = gh6415_nomatch_k(E), + Res = gh6415_nomatch_l(E), + Res. + +gh6415_nomatch_a(#{0 := <<_:0>>, 0 := <<_:8>>}) -> ok; +gh6415_nomatch_a(_) -> error. + +gh6415_nomatch_b(#{0 := <<_:16>>, 0 := <<_:8>>}) -> ok; +gh6415_nomatch_b(_) -> error. + +gh6415_nomatch_c(#{0 := <<>>, 0 := <<_:8>>}) -> ok; +gh6415_nomatch_c(_) -> error. + +gh6415_nomatch_d(#{0 := <<_:8>>, 0 := <<_:0>>}) -> ok; +gh6415_nomatch_d(_) -> error. + +gh6415_nomatch_e(#{0 := <<_:8>>, 0 := <<>>}) -> ok; +gh6415_nomatch_e(_) -> error. + +gh6415_nomatch_f(#{0 := <<_:8>>, 0 := <<_:16>>}) -> ok; +gh6415_nomatch_f(_) -> error. + +gh6415_nomatch_g(#{0 := <<_>> = <<_, 0>>}) -> ok; +gh6415_nomatch_g(_) -> error. + +gh6415_nomatch_h(#{0 := <>, 0 := <>}) -> ok; +gh6415_nomatch_h(_) -> error. + +gh6415_nomatch_i(<<_>> = <<_:16,T/binary>>) -> + _ = binary_to_list(T), + ok; +gh6415_nomatch_i(_) -> + error. + +gh6415_nomatch_j(<<_>> = <>) -> + {X,T}; +gh6415_nomatch_j(_) -> + error. + +gh6415_nomatch_k(#{0 := <<_>>, 0 := <<_, _:(0 div 0)>>}) -> + ok; +gh6415_nomatch_k(_) -> + error. + +gh6415_nomatch_l(Bin) -> + case Bin of + <> = <<_:8>> -> + ok; + _ -> + error + end. + +gh6415_match_a(<<_>> = <>) -> + X. + +gh6415_match_b(<<_>> = <>) -> + {X,T}. + +gh6415_match_c() -> + case catch <<0 || true>> of + #{[] := <<_>>, [] := <>} -> + X + end. + +gh6415_match_d(<<_, _:(true andalso 0)>> = <<_>>) -> + ok. + +gh6415_match_e(<<_, _:(true andalso 0), Size, _:Size>> = <<_, Size, _:Size>>) -> + ok. + +gh6415_match_f(<<_:(true andalso 0), Size, Var:Size>> = + <> = + <<17,Var:17,_:(true andalso 0)>>) -> + Var; +gh6415_match_f(_) -> + error. + %%% Utilities. -id(I) -> I. +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..55ff6f7d6309 100644 --- a/lib/compiler/test/match_SUITE.erl +++ b/lib/compiler/test/match_SUITE.erl @@ -26,7 +26,8 @@ selectify/1,deselectify/1,underscore/1,match_map/1,map_vars_used/1, coverage/1,grab_bag/1,literal_binary/1, unary_op/1,eq_types/1,match_after_return/1,match_right_tuple/1, - tuple_size_in_try/1,match_boolean_list/1]). + tuple_size_in_try/1,match_boolean_list/1, + heisen_variables/1]). -include_lib("common_test/include/ct.hrl"). @@ -43,7 +44,8 @@ groups() -> underscore,match_map,map_vars_used,coverage, grab_bag,literal_binary,unary_op,eq_types, match_after_return,match_right_tuple, - tuple_size_in_try,match_boolean_list]}]. + tuple_size_in_try,match_boolean_list, + heisen_variables]}]. init_per_suite(Config) -> @@ -151,14 +153,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 +266,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 +288,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_2((A=B)=(A=C)) -> +multiple_aliases_1b(Expr) -> + (A=B) = (C=D) = Expr, + {A,B,C,D}. + +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_3((A={_,_}=B)={_,_}=C) -> +multiple_aliases_3b(Expr) -> + (A={_,_}=B) = {_,_} = C = Expr, {A,B,C}. -list_alias1([a,b]=[X,Y]) -> +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_4b(Expr) -> + (A=[_,_,_]=B) = [_,_,_] = C = Expr, + {A,B,C}. + +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_alias2([X,Y]=[a,b]) -> +list_alias2b(Expr) -> + [X,Y] = [a,b] = Expr, {X,Y}. -list_alias3([X,b]=[a,Y]) -> +list_alias3a([X,b]=[a,Y]) -> + {X,Y}. + +list_alias3b(Expr) -> + [X,b] = [a,Y]= Expr, {X,Y}. non_matching_aliases(_Config) -> @@ -320,6 +407,9 @@ non_matching_aliases(_Config) -> {'EXIT',{{case_clause,whatever},_}} = (catch pike1(whatever)), {'EXIT',{{case_clause,whatever},_}} = (catch pike2(whatever)), + {'EXIT',{badarith,_}} = catch squid(a), + {'EXIT',{{badmatch,43},_}} = catch squid(42), + ok. mixed_aliases(<> = x) -> {a,X}; @@ -402,6 +492,11 @@ pike2(X) -> end, Var. +squid(E) -> + ([X] = {Y}) = V = E + 1, + {V,X + Y}. + + %% OTP-7018. match_in_call(Config) when is_list(Config) -> @@ -1018,4 +1113,16 @@ match_boolean_list(Config) when is_list(Config) -> [false | _] -> ok end. +heisen_variables(_Config) -> + {'EXIT',{{badmatch,3},_}} = catch gh_6516_scope1(), + {'EXIT',{{badmatch,3},_}} = catch gh_6516_scope2(), + + ok. + +gh_6516_scope1() -> + {X = 4, X = 3}. + +gh_6516_scope2() -> + {X = 4, _ = X = 3}. + id(I) -> I. diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl index 9b860de951c5..48a9bdf8cbc0 100644 --- a/lib/compiler/test/warnings_SUITE.erl +++ b/lib/compiler/test/warnings_SUITE.erl @@ -102,8 +102,7 @@ pattern(Config) when is_list(Config) -> [warn_unused_vars], {warnings, [{{2,15},v3_core,{nomatch,pattern}}, - {{6,20},v3_core,{nomatch,pattern}}, - {{11,18},v3_core,{nomatch,pattern}} + {{6,20},v3_core,{nomatch,pattern}} ]}}], [] = run(Config, Ts), ok. diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 1654e119d12b..31eee8b36022 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. @@ -2633,8 +2535,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); @@ -3985,14 +3886,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 d98dea1e2f73..c8c9bd891c89 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. @@ -2298,22 +2305,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}). @@ -2330,11 +2337,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}). @@ -2354,42 +2360,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) -> <> = <>, @@ -2403,27 +2408,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, @@ -3888,31 +3936,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..453ca0bb8236 100644 --- a/system/doc/reference_manual/expressions.xml +++ b/system/doc/reference_manual/expressions.xml @@ -138,18 +138,22 @@ member(_Elem, []) -> Name1 [H|T] {error,Reason} -

Patterns are allowed in clause heads, case and - receive expressions, and match expressions.

+

Patterns are allowed in clause heads, + case expressions, + receive expressions, + and + match expressions.

- Match Operator = in Patterns + + The Compound Pattern Operator

If Pattern1 and Pattern2 are valid patterns, the following is also a valid pattern:

 Pattern1 = Pattern2

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

+ Pattern2 are matched against the term. The idea behind + this feature is to avoid reconstruction of terms.

Example:

 f({connect,From,To,Number,Options}, To) ->
@@ -163,6 +167,11 @@ f({connect,_,To,_,_} = Signal, To) ->
     ...;
 f(Signal, To) ->
     ignore.
+ +

The compound pattern operator does not imply that its operands + are matched in any particular order. That means that it is not + legal to bind a variable in Pattern1 and use it in Pattern2, + or vice versa.

@@ -192,22 +201,121 @@ case {Value, Result} of
- Match -

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

+ + The Match Operator +

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]
+ +

Because multiple match operators are evaluated from right to left, + it means that:

+ +
+Pattern1 = Pattern2 = . . . = PatternN = Expression
+ +

is equivalent to:

+
+Temporary = Expression,
+PatternN = Temporary,
+   .
+   .
+   .,
+Pattern2 = Temporary,
+Pattern = Temporary
+
+ +
+ The Match Operator and the Compound Pattern Operator +

This is an advanced section, which references to topics not + yet introduced. It can safely be skipped on a first + reading.

+ +

The = character is used to denote two similar but + distinct operators: the match operator and the compound pattern + operator. Which one is meant is determined by context.

+ +

The compound pattern operator is used to construct a + compound pattern from two patterns. Compound patterns are accepted + everywhere a pattern is accepted. A compound pattern matches if + all of its constituent patterns match. It is not legal for a + pattern that is part of a compound pattern to use variables (as + keys in map patterns or sizes in binary patterns) bound in other + sub patterns of the same compound pattern.

+

Examples:

+ +
+1> fun(#{Key := Value} = #{key := Key}) -> Value end.
+* 1:7: variable 'Key' is unbound
+2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}).
+{{1,2},3}
+3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>).
+{42,43,10795}
+ +

The match operator is allowed everywhere an expression + is allowed. It is used to match the value of an expression to a pattern. + If multiple match operators are applied in sequence, they will be + evaluated from right to left.

+ +

Examples:

+
+1> M = #{key => key2, key2 => value}.
+#{key => key2,key2 => value}
+2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
+value
+3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
+value
+4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
+* 1:12: variable 'Key' is unbound
+5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
+42
+ +

The expression at prompt 2> first matches the value of + variable M against pattern #{key := Key}, binding + variable Key. It then matches the value of M against + pattern #{Key := Value} using variable Key as the + key, binding variable Value.

+ +

The expression at prompt 3> matches expression + (#{key := Key} = M) against pattern #{Key := + Value}. The expression inside the parentheses is evaluated + first. That is, M is matched against #{key := Key}, + and then the value of M is matched against pattern #{Key + := Value}. That is the same evaluation order as in 2; + therefore, the parentheses are redundant.

+ +

In the expression at prompt 4> the expression M + is matched against a pattern inside parentheses. Since the + construct inside the parentheses is a pattern, the = that + separates the two patterns is the compound pattern operator + (not the match operator). The match fails because the two + sub patterns are matched at the same time, and the variable + Key is therefore not bound when matching against pattern + #{Key := Value}.

+ +

In the expression at prompt 5> the expressions + inside the block + expression are evaluated first, binding variable + Y and creating a binary. The binary is then matched + against pattern <<X:Y>> using the value of + Y as the size of the segment.

@@ -1668,6 +1776,7 @@ end
+ Block Expressions
 begin
@@ -2003,6 +2112,11 @@ end
Operator Precedence +

The = operator in the table is the + match operator. + The character = can also denote the + compound pattern operator, + which can only be used in patterns.

When evaluating an expression, the operator with the highest priority is evaluated first. Operators with the same priority are evaluated according to their associativity.