%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(match_SUITE).
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
pmatch/1,mixed/1,aliases/1,non_matching_aliases/1,
match_in_call/1,untuplify/1,shortcut_boolean/1,letify_guard/1,
selectify/1,deselectify/1,underscore/1,match_map/1,map_vars_used/1,
coverage/1,grab_bag/1,literal_binary/1,
unary_op/1]).
-include_lib("common_test/include/ct.hrl").
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[{group,p}].
groups() ->
[{p,[parallel],
[pmatch,mixed,aliases,non_matching_aliases,
match_in_call,untuplify,
shortcut_boolean,letify_guard,selectify,deselectify,
underscore,match_map,map_vars_used,coverage,
grab_bag,literal_binary,unary_op]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
Config.
end_per_suite(_Config) ->
ok.
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
pmatch(Config) when is_list(Config) ->
ok = doit(1),
ok = doit(2),
{error,baz} = doit(3),
{error,foobar} = doit(4),
ok.
%% Thanks to Tobias Lindahl (HiPE).
-define(FOO(X),
case X of
1 -> foo;
2 -> bar;
3 -> baz;
4 -> foobar
end).
doit(X) ->
case ?FOO(X) of
foo -> ok;
bar -> ok;
Other -> {error, Other}
end.
mixed(Config) when is_list(Config) ->
glufs = mixit(1),
klafs = mixit(2),
fnurra = mixit(3),
usch = mixit(4),
{error,blurf} = mixit(5),
{error,87987987} = mixit(6),
{error,{a,b,c}} = mixit(7),
ok.
mixit(X) ->
case case X of
1 -> a;
2 -> b;
3 -> 42;
4 -> 77;
4+1 -> blurf;
5+1 -> 87987987;
6+1 -> {a,b,c}
end of
a -> glufs;
b -> klafs;
42 -> fnurra;
77 -> usch;
Other -> {error,Other}
end.
aliases(Config) when is_list(Config) ->
%% Lists/strings.
ok = str_alias("abc"),
ok = str_alias("def"),
ok = str_alias("ghi"),
ok = str_alias("klm"),
ok = str_alias("qrs"),
ok = str_alias("xy"),
ok = str_alias(""),
ok = str_alias([]),
error = str_alias("blurf"),
%% Characters/integers.
ok = char_alias($v),
ok = char_alias(118),
ok = char_alias($w),
ok = char_alias(119),
ok = char_alias(42),
ok = char_alias(3.0),
error = char_alias($_),
error = char_alias(0),
{42,42,42} = three(42),
{1,42,99,1,42,99} = tuple_alias({1,42,99}),
{-10,20,-10,20,-10,20} = tuple_alias({-10,20}),
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}),
%% Lists/literals.
{a,b} = list_alias1([a,b]),
{a,b} = list_alias2([a,b]),
{a,b} = list_alias3([a,b]),
ok.
str_alias(V) ->
Res = str_alias_1(V),
Res = str_alias_2(V).
str_alias_1([$a,$b,$c]="abc"="a"++[$b,$c]=[97,98,99]) -> ok;
str_alias_1([$d|"ef"]="def") -> ok;
str_alias_1([$g|"hi"]="g"++"hi"="gh"++"i"="ghi"++"") -> ok;
str_alias_1("k"++"lm"=[$k|"lm"]) -> ok;
str_alias_1([113,114,115]="qrs"=[$q,$r,$s]="q"++"r"++"s") -> ok;
str_alias_1([$x,$y]="xy") -> ok;
str_alias_1(""=[]) -> ok;
str_alias_1(_) -> error.
%% Make sure that different line numbers do not matter.
str_alias_2([$a,$b,$c]=
"abc"=
"a"++[$b,$c
]=
[97,98,99
]) -> ok;
str_alias_2([$d|"ef"]=
"def") -> ok;
str_alias_2([$g|"hi"]=
"g"++"hi"=
"gh"++"i"=
"ghi"++"") -> ok;
str_alias_2("k"++"lm"=
[$k|"lm"
]) -> ok;
str_alias_2([113,114,115]=
"qrs"=[$q,$r,$s
]=
"q"++"r"++"s") -> ok;
str_alias_2([$x,$y]=
"xy") -> ok;
str_alias_2(""=
[]) -> ok;
str_alias_2(_) -> error.
char_alias(V) ->
Res = char_alias_1(V),
Res = char_alias_2(V).
char_alias_1(118=$v) -> ok;
char_alias_1($w=119) -> ok;
char_alias_1(42=42) -> ok;
char_alias_1(3.0=3.0) -> ok;
char_alias_1(_) -> error.
char_alias_2(118=
$v) -> ok;
char_alias_2($w=
119) -> ok;
char_alias_2(42=
42) -> ok;
char_alias_2(3.0=
3.0) -> ok;
char_alias_2(_) -> error.
three(V) ->
Res = three_1(V),
Res = three_2(V).
three_1(A=B=C) ->
{A,B,C}.
three_2(A=
B=
C) ->
{A,B,C}.
tuple_alias({A,B,C}={X,Y,Z}) ->
{A,B,C,X,Y,Z};
tuple_alias({A,B}={C,D}={E,F}) ->
{A,B,C,D,E,F}.
tup_lit_alias({A,B,C}={1,2,3}) ->
A+B+C.
tup_lit_alias_rev({1,2,3}={A,B,C}) ->
A+B+C.
multiple_aliases_1((A=B)=(C=D)) ->
{A,B,C,D}.
multiple_aliases_2((A=B)=(A=C)) ->
{A,B,C}.
multiple_aliases_3((A={_,_}=B)={_,_}=C) ->
{A,B,C}.
list_alias1([a,b]=[X,Y]) ->
{X,Y}.
list_alias2([X,Y]=[a,b]) ->
{X,Y}.
list_alias3([X,b]=[a,Y]) ->
{X,Y}.
non_matching_aliases(_Config) ->
none = mixed_aliases(<<42>>),
none = mixed_aliases([b]),
none = mixed_aliases([d]),
none = mixed_aliases({a,42}),
none = mixed_aliases(42),
none = mixed_aliases(<<6789:16>>),
none = mixed_aliases(#{key=>value}),
{'EXIT',{{badmatch,42},_}} = (catch nomatch_alias(42)),
{'EXIT',{{badmatch,job},_}} = (catch entirely()),
{'EXIT',{{badmatch,associates},_}} = (catch printer()),
{'EXIT',{{badmatch,borogoves},_}} = (catch tench()),
put(perch, 0),
{'EXIT',{{badmatch,{spine,42}},_}} = (catch perch(42)),
1 = erase(perch),
put(salmon, 0),
{'EXIT',{{badmatch,mimsy},_}} = (catch salmon()),
1 = erase(salmon),
put(shark, 0),
{'EXIT',{{badmatch,_},_}} = (catch shark()),
1 = erase(shark),
{'EXIT',{{badmatch,_},_}} = (catch radio(research)),
ok.
mixed_aliases(<<X:8>> = x) -> {a,X};
mixed_aliases([b] = <<X:8>>) -> {b,X};
mixed_aliases(<<X:8>> = {a,X}) -> {c,X};
mixed_aliases([X] = <<X:8>>) -> {d,X};
mixed_aliases(<<X:16>> = X) -> {e,X};
mixed_aliases(X = <<X:16>>) -> {f,X};
mixed_aliases(<<X:16,_/binary>> = X) -> {g,X};
mixed_aliases(X = <<X:16,_/binary>>) -> {h,X};
mixed_aliases(X = #{key:=X}) -> {i,X};
mixed_aliases(#{key:=X} = X) -> {j,X};
mixed_aliases([X] = #{key:=X}) -> {k,X};
mixed_aliases(#{key:=X} = [X]) -> {l,X};
mixed_aliases({a,X} = #{key:=X}) -> {m,X};
mixed_aliases(#{key:=X} = {a,X}) -> {n,X};
mixed_aliases(_) -> none.
nomatch_alias(I) ->
{ok={A,B}} = id(I),
{A,B}.
entirely() ->
0(((Voice = true) = cool) = job),
[receive _ -> Voice end || banking <- printer].
printer() ->
{[Indoor] = [] = associates},
[ireland || Indoor <- Indoor].
tench() ->
E = begin
[A] = [] = borogoves,
A + 1
end,
E + 7 * A.
perch(X) ->
begin
put(perch, get(perch)+1),
[A] = [] = {spine,X}
end.
salmon() ->
{put(salmon, get(salmon)+1),#{key:=([A]=[])}=mimsy,exit(fail)},
A + 10.
shark() ->
(hello = there) = (catch shark(put(shark, get(shark)+1), a = b)).
shark(_, _) ->
ok.
radio(research) ->
(connection = proof) =
(catch erlang:trace_pattern(catch mechanisms + assist,
summary = mechanisms)).
%% OTP-7018.
match_in_call(Config) when is_list(Config) ->
mac_a(0),
mac_b(1),
mac_c(42),
mac_d(42),
mac_e({gurka,42}),
[{2,2},{2,2}] = mac_lc([{2,any},{2,2}]),
{'EXIT',_} = (catch mac_lc([{1,1}])),
ok.
mac_a(X) ->
id(_Gurka = {gurka,X}),
ok.
mac_b(X) ->
id(Gurka = {gurka,X}),
gurka(Gurka, X),
ok.
mac_c(X) ->
id(Gurka = Yxa = {gurka,X}),
id({Gurka,Yxa}),
ok.
mac_d(X) ->
id({gurka,42} = {gurka,X}),
ok.
mac_e(X) ->
id({gurka,42} = X),
ok.
mac_lc(E) ->
Res = mac_lc1(E),
Res = mac_lc2(E).
mac_lc1(E) ->
[{X,Y} ||
{X,_} <- E,
(Y = X) =:= (Y = 1 + 1)].
mac_lc2(E) ->
[{X,Y} ||
{X,_} <- E,
(Y = X) =:= (Y = 2)].
gurka({gurka,X}, X) -> ok.
untuplify(Config) when is_list(Config) ->
%% We do this to cover sys_core_fold:unalias_pat/1.
{1,2,3,4,alias,{[1,2],{3,4},alias}} = untuplify_1([1,2], {3,4}, alias),
error = untuplify_1([1,2], {3,4}, 42),
ok.
untuplify_1(A, B, C) ->
case {A,B,C} of
{[X,Y],{Z,W},alias=Alias}=Top ->
{X,Y,Z,W,Alias,Top};
[_,_]=CantMatch ->
CantMatch;
_ ->
error
end.
%% Coverage of beam_dead:shortcut_boolean_label/4.
shortcut_boolean(Config) when is_list(Config) ->
false = shortcut_boolean_1([0]),
true = shortcut_boolean_1({42}),
maybe = shortcut_boolean_1(self()),
{'EXIT',_} = (catch shortcut_boolean_1([a,b])),
{'EXIT',_} = (catch shortcut_boolean_1({a,b})),
ok.
shortcut_boolean_1(X) ->
Outer = case not is_pid(X) of
true ->
V = case X of
[_] -> true;
{_} -> false
end,
not V;
false ->
maybe
end,
id(Outer).
%% Test sys_core_fold:letify_guard/3.
letify_guard(Config) when is_list(Config) ->
{-15,a} = letify_guard(-15, a),
5 = letify_guard(2, 3),
ok.
letify_guard(A, B) ->
case {A,B} of
%% The tuple will be built in the guard...
Z when tuple_size(Z) =:= 2, element(1, Z) < 0 ->
%% ... and again here.
Z;
{X,Y} -> X+Y
end.
%% Test combining of is_eq_exact instructions to select_val
%% instructions in beam_dead and beam_peep.
selectify(Config) when is_list(Config) ->
integer = sel_different_types({r,42}),
atom = sel_different_types({r,forty_two}),
float = sel_different_types({r,100.0}),
none = sel_different_types({r,18}),
{'EXIT',_} = (catch sel_different_types([a,b,c])),
integer = sel_same_value({r,42}),
error = sel_same_value({r,100}),
error = sel_same_value(a),
integer42 = sel_same_value2(42),
integer43 = sel_same_value2(43),
error = sel_same_value2(44),
ok.
sel_different_types({r,_}=T) when element(2, T) =:= forty_two ->
atom;
sel_different_types({r,_}=T) when element(2, T) =:= 42 ->
integer;
sel_different_types({r,_}=T) when element(2, T) =:= 100.0 ->
float;
sel_different_types({r,_}) ->
none.
sel_same_value({r,V}) when V =:= 42 ->
integer;
sel_same_value({r,V}) when V =:= 42 ->
integer42;
sel_same_value(_) ->
error.
sel_same_value2(V) when V =:= 42 ->
integer42;
sel_same_value2(V) when V =:= 42; V =:= 43 ->
integer43;
sel_same_value2(_) ->
error.
%% Test deconstruction of select_val instructions to regular tests
%% with zero or one values left.
deselectify(Config) when is_list(Config) ->
one_or_other = desel_tuple_arity({1}),
two = desel_tuple_arity({1,1}),
one_or_other = desel_tuple_arity({1,1,1}),
one_or_other = dsel_integer(1),
two = dsel_integer(2),
one_or_other = dsel_integer(3),
one_or_other = dsel_integer_typecheck(1),
two = dsel_integer_typecheck(2),
one_or_other = dsel_integer_typecheck(3),
one_or_other = dsel_atom(one),
two = dsel_atom(two),
one_or_other = dsel_atom(three),
one_or_other = dsel_atom_typecheck(one),
two = dsel_atom_typecheck(two),
one_or_other = dsel_atom_typecheck(three),
%% Cover deconstruction of select_val instructions in
%% beam_peep.
stop = dsel_peek_0(stop),
ignore = dsel_peek_0(ignore),
Config = dsel_peek_0(Config),
stop = dsel_peek_1(stop, any),
Config = dsel_peek_1(ignore, Config),
other = dsel_peek_1(other, ignored),
0 = dsel_peek_2(0, any),
Config = dsel_peek_2(1, Config),
2 = dsel_peek_2(2, ignored),
true = dsel_peek_3(true),
false = dsel_peek_3(false),
{error,Config} = dsel_peek_3(Config),
ok.
%% The following will be optimized by the sharing optimizations
%% in beam_ssa_opt.
desel_tuple_arity(Tuple) when is_tuple(Tuple) ->
case Tuple of
{_} -> one_or_other;
{_,_} -> two;
_ -> one_or_other
end.
dsel_integer(Val) ->
case Val of
1 -> one_or_other;
2 -> two;
_ -> one_or_other
end.
dsel_integer_typecheck(Val) when is_integer(Val) ->
case Val of
1 -> one_or_other;
2 -> two;
_ -> one_or_other
end.
dsel_atom(Val) ->
case Val of
one -> one_or_other;
two -> two;
_ -> one_or_other
end.
dsel_atom_typecheck(Val) when is_atom(Val) ->
case Val of
one -> one_or_other;
two -> two;
_ -> one_or_other
end.
%% The following functions are carefully crafted so that the sharing
%% optimizations in beam_ssa_opt can't be applied. After applying the
%% beam_jump:eliminate_moves/1 optimization and beam_clean:clean_labels/1
%% has unified labels, beam_peep is able to optimize these functions.
dsel_peek_0(A0) ->
case id(A0) of
stop -> stop;
ignore -> ignore;
A -> A
end.
dsel_peek_1(A0, B) ->
case id(A0) of
stop -> stop;
ignore -> B;
A -> A
end.
dsel_peek_2(A0, B) ->
case id(A0) of
0 -> 0;
1 -> B;
A -> A
end.
dsel_peek_3(A0) ->
case id(A0) of
true -> true;
false -> false;
Other -> {error,Other}
end.
underscore(Config) when is_list(Config) ->
case Config of
[] ->
%% Assignment to _ at the end of a construct.
_ = length(Config);
[_|_] ->
%% Assignment to _ at the end of a construct.
_ = list_to_tuple(Config)
end,
_ = is_list(Config),
ok.
-record(s, {map,t}).
match_map(Config) when is_list(Config) ->
Map = #{key=>{x,y},ignore=>anything},
#s{map=Map,t={x,y}} = do_match_map(#s{map=Map}),
{a,#{k:={a,b,c}}} = do_match_map_2(#{k=>{a,b,c}}),
ok.
do_match_map(#s{map=#{key:=Val}}=S) ->
%% Would crash with a 'badarg' exception.
S#s{t=Val}.
do_match_map_2(Map) ->
case {a,Map} of
{a,#{k:=_}}=Tuple ->
Tuple
end.
map_vars_used(Config) when is_list(Config) ->
{some,value} = do_map_vars_used(a, b, #{{a,b}=>42,v=>{some,value}}),
ok.
do_map_vars_used(X, Y, Map) ->
case {X,Y} of
T ->
%% core_lib:is_var_used/2 would not consider T used.
#{T:=42,v:=Val} = Map,
Val
end.
coverage(Config) when is_list(Config) ->
%% Cover beam_dead.
ok = coverage_1(x, a),
ok = coverage_1(x, b),
%% Cover sys_pre_expand.
ok = coverage_3("abc").
coverage_1(B, Tag) ->
case Tag of
a -> coverage_2(1, a, B);
b -> coverage_2(2, b, B)
end.
coverage_2(1, a, x) -> ok;
coverage_2(2, b, x) -> ok.
coverage_3([$a]++[]++"bc") -> ok.
grab_bag(_Config) ->
[_|T] = id([a,b,c]),
[b,c] = id(T),
T1 = fun() ->
[_|_] = x
end,
{'EXIT',_} = (catch T1()),
T2 = fun(A, B) ->
case {{element(1, A),element(2, B)},
{element(2, A),element(2, B)}} of
{Same,Same} -> ok;
{{0,1},{up,X}} -> id(X);
{_,{X,_}} -> id(X)
end
end,
ok = T2({a,a,z,z}, {z,a,z}),
1 = T2({0,up}, {zzz,1}),
y = T2({x,y}, {a,z,z}),
%% OTP-5244.
L = [{stretch,0,0},
{bad,[]},
{bad,atom},
{bad,0},
{bad,16#AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA},
{bad,16#555555555555555555555555555555555555555555555555555}],
ok = grab_bag_remove_failure(L, unit, 0),
{42,<<43,44>>} = grab_bag_single_valued(<<42,43,44>>),
empty_list = grab_bag_single_valued([]),
empty_tuple = grab_bag_single_valued({}),
ok.
grab_bag_remove_failure([], _Unit, _MaxFailure) ->
ok;
grab_bag_remove_failure([{bad,Bad}|_], _Unit, _MaxFailure) ->
Bad;
grab_bag_remove_failure([{stretch,_,Mi}=Stretch | Specs], Unit, _MaxFailure) ->
{MinMax,NewMaxFailure} = id({min,1}),
case {MinMax,grab_bag_remove_failure(Specs, Unit, NewMaxFailure)} of
{min,{NewMaxFailure,Rest}} ->
{done,[{fixed,Mi} | Rest]};
{min,_} when Specs =/= [] ->
grab_bag_remove_failure([Stretch|tl(Specs)], Unit, NewMaxFailure);
{min,_} ->
ok
end.
%% Cover a line v3_kernel that places binary matching first.
grab_bag_single_valued(<<H,T/bytes>>) -> {H,T};
grab_bag_single_valued([]) -> empty_list;
grab_bag_single_valued({}) -> empty_tuple.
%% Regression in 19.0, reported by Alexei Sholik
literal_binary(_Config) ->
3 = literal_binary_match(bar, <<"y">>),
%% While we are at it, also test the remaining code paths
%% in literal_binary_match/2.
1 = literal_binary_match(bar, <<"x">>),
2 = literal_binary_match(foo, <<"x">>),
3 = literal_binary_match(foo, <<"y">>),
fail = literal_binary_match(bar, <<"z">>),
fail = literal_binary_match(foo, <<"z">>),
ok.
literal_binary_match(bar, <<"x">>) -> 1;
literal_binary_match(_, <<"x">>) -> 2;
literal_binary_match(_, <<"y">>) -> 3;
literal_binary_match(_, _) -> fail.
unary_op(Config) ->
%% ERL-514. This test case only verifies that the code
%% calculates the correct result, not that the generated
%% code is optimial.
{non_associative,30} = unary_op_1('&'),
{non_associative,300} = unary_op_1('^'),
{non_associative,300} = unary_op_1('not'),
{non_associative,300} = unary_op_1('+'),
{non_associative,300} = unary_op_1('-'),
{non_associative,300} = unary_op_1('~~~'),
{non_associative,300} = unary_op_1('!'),
{non_associative,320} = unary_op_1('@'),
error = unary_op_1(Config),
error = unary_op_1(abc),
error = unary_op_1(42),
ok.
unary_op_1(Vop@1) ->
%% If all optimizations are working as they should, there should
%% be no stack frame and all '=:=' tests should be coalesced into
%% a single select_val instruction.
case Vop@1 =:= '&' of
true ->
{non_associative,30};
false ->
case
case Vop@1 =:= '^' of
true ->
true;
false ->
case Vop@1 =:= 'not' of
true ->
true;
false ->
case Vop@1 =:= '+' of
true ->
true;
false ->
case Vop@1 =:= '-' of
true ->
true;
false ->
case Vop@1 =:= '~~~' of
true ->
true;
false ->
Vop@1 =:= '!'
end
end
end
end
end
of
true ->
{non_associative,300};
false ->
case Vop@1 =:= '@' of
true ->
{non_associative,320};
false ->
error
end
end
end.
id(I) -> I.