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