diff options
author | Björn Gustavsson <bjorn@erlang.org> | 2022-11-03 09:33:24 +0100 |
---|---|---|
committer | Björn Gustavsson <bjorn@erlang.org> | 2022-12-13 12:35:45 +0100 |
commit | 6a9ebe62aadd241424d9f15998e7ac488b20ac6b (patch) | |
tree | 67b558ab522bbbc2108c7def562b72079ffcf632 /lib/compiler/test/match_SUITE.erl | |
parent | 58b62e50263ac9f9a524bc14bcbef2cd751d71bd (diff) | |
download | erlang-6a9ebe62aadd241424d9f15998e7ac488b20ac6b.tar.gz |
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) ->
<<A:8>> = <<X:4,Y:4>> = 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| <<A:8>> = <<X:4,Y:4>> = 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
Diffstat (limited to 'lib/compiler/test/match_SUITE.erl')
-rw-r--r-- | lib/compiler/test/match_SUITE.erl | 139 |
1 files changed, 123 insertions, 16 deletions
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl index 5fc487e7a9..55ff6f7d63 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:8>> = 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. |