summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksei Magusev <lexmag@me.com>2018-05-07 17:25:08 +0200
committerGitHub <noreply@github.com>2018-05-07 17:25:08 +0200
commit2a7abb7f72420c1cee70d0878c8c085d4d515a46 (patch)
tree88f792662a37595f37d8670ed8891b988bf88f80
parentf84770ebf0646da572c8211ddb72e03b34a994a2 (diff)
downloadelixir-2a7abb7f72420c1cee70d0878c8c085d4d515a46.tar.gz
Implement illegal_bin_pattern check in Elixir pass (#7444)
-rw-r--r--lib/elixir/src/elixir_expand.erl48
-rw-r--r--lib/elixir/test/elixir/kernel/expansion_test.exs83
2 files changed, 129 insertions, 2 deletions
diff --git a/lib/elixir/src/elixir_expand.erl b/lib/elixir/src/elixir_expand.erl
index ef6ebe1a8..4f1b96130 100644
--- a/lib/elixir/src/elixir_expand.erl
+++ b/lib/elixir/src/elixir_expand.erl
@@ -8,7 +8,8 @@
expand({'=', Meta, [Left, Right]}, E) ->
assert_no_guard_scope(Meta, "=", E),
{ERight, ER} = expand(Right, E),
- {ELeft, EL} = elixir_clauses:match(fun expand/2, Left, E),
+ {ELeft, EL} = elixir_clauses:match(fun expand/2, Left, E),
+ refute_parallel_bitstring_match(ELeft, ERight, E, ?key(E, context) == match),
{{'=', Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
%% Literal operators
@@ -921,6 +922,45 @@ assert_generator_start(Meta, _, E) ->
%% Assertions
+refute_parallel_bitstring_match({'<<>>', _, _}, {'<<>>', Meta, _} = Arg, E, true) ->
+ form_error(Meta, ?key(E, file), ?MODULE, {parallel_bitstring_match, Arg});
+refute_parallel_bitstring_match(Left, {'=', _Meta, [MatchLeft, MatchRight]}, E, Parallel) ->
+ refute_parallel_bitstring_match(Left, MatchLeft, E, true),
+ refute_parallel_bitstring_match(Left, MatchRight, E, Parallel);
+refute_parallel_bitstring_match([_ | _] = Left, [_ | _] = Right, E, Parallel) ->
+ refute_parallel_bitstring_match_each(Left, Right, E, Parallel);
+refute_parallel_bitstring_match({Left1, Left2}, {Right1, Right2}, E, Parallel) ->
+ refute_parallel_bitstring_match_each([Left1, Left2], [Right1, Right2], E, Parallel);
+refute_parallel_bitstring_match({'{}', _, Args1}, {'{}', _, Args2}, E, Parallel) ->
+ refute_parallel_bitstring_match_each(Args1, Args2, E, Parallel);
+refute_parallel_bitstring_match({'%{}', _, Args1}, {'%{}', _, Args2}, E, Parallel) ->
+ refute_parallel_bitstring_match_map_field(lists:sort(Args1), lists:sort(Args2), E, Parallel);
+refute_parallel_bitstring_match({'%', _, [_, Args]}, Right, E, Parallel) ->
+ refute_parallel_bitstring_match(Args, Right, E, Parallel);
+refute_parallel_bitstring_match(Left, {'%', _, [_, Args]}, E, Parallel) ->
+ refute_parallel_bitstring_match(Left, Args, E, Parallel);
+refute_parallel_bitstring_match(_Left, _Right, _E, _Parallel) ->
+ ok.
+
+refute_parallel_bitstring_match_each([Arg1 | Rest1], [Arg2 | Rest2], E, Parallel) ->
+ refute_parallel_bitstring_match(Arg1, Arg2, E, Parallel),
+ refute_parallel_bitstring_match_each(Rest1, Rest2, E, Parallel);
+refute_parallel_bitstring_match_each(_List1, _List2, _E, _Parallel) ->
+ ok.
+
+refute_parallel_bitstring_match_map_field([{Key, Val1} | Rest1], [{Key, Val2} | Rest2], E, Parallel) ->
+ refute_parallel_bitstring_match(Val1, Val2, E, Parallel),
+ refute_parallel_bitstring_match_map_field(Rest1, Rest2, E, Parallel);
+refute_parallel_bitstring_match_map_field([Field1 | Rest1] = Args1, [Field2 | Rest2] = Args2, E, Parallel) ->
+ case Field1 > Field2 of
+ true ->
+ refute_parallel_bitstring_match_map_field(Args1, Rest2, E, Parallel);
+ false ->
+ refute_parallel_bitstring_match_map_field(Rest1, Args2, E, Parallel)
+ end;
+refute_parallel_bitstring_match_map_field(_Args1, _Args2, _E, _Parallel) ->
+ ok.
+
assert_module_scope(Meta, Kind, #{module := nil, file := File}) ->
form_error(Meta, File, ?MODULE, {invalid_expr_in_scope, "module", Kind});
assert_module_scope(_Meta, _Kind, #{module:=Module}) -> Module.
@@ -1110,4 +1150,8 @@ format_error({struct_comparison, StructExpr}) ->
format_error(caller_not_allowed) ->
"__CALLER__ is available only inside defmacro and defmacrop";
format_error(stacktrace_not_allowed) ->
- "__STACKTRACE__ is available only inside catch and rescue clauses of try expressions".
+ "__STACKTRACE__ is available only inside catch and rescue clauses of try expressions";
+format_error({parallel_bitstring_match, Expr}) ->
+ Message =
+ "binary patterns cannot be matched in parallel using \"=\", excess pattern: ~ts",
+ io_lib:format(Message, ['Elixir.Macro':to_string(Expr)]).
diff --git a/lib/elixir/test/elixir/kernel/expansion_test.exs b/lib/elixir/test/elixir/kernel/expansion_test.exs
index 21136fcfa..5672bfea6 100644
--- a/lib/elixir/test/elixir/kernel/expansion_test.exs
+++ b/lib/elixir/test/elixir/kernel/expansion_test.exs
@@ -1853,6 +1853,89 @@ defmodule Kernel.ExpansionTest do
end
describe "bitstrings" do
+ test "parallel match" do
+ assert expand(quote(do: <<foo>> = <<bar>>)) |> clean_meta([:alignment]) ==
+ quote(do: <<foo::integer()>> = <<bar()::integer()>>)
+
+ assert expand(quote(do: <<foo>> = baz = <<bar>>)) |> clean_meta([:alignment]) ==
+ quote(do: <<foo::integer()>> = baz = <<bar()::integer()>>)
+
+ assert expand(quote(do: <<foo>> = {<<baz>>} = bar())) |> clean_meta([:alignment]) ==
+ quote(do: <<foo::integer()>> = {<<baz::integer()>>} = bar())
+
+ message = ~r"binary patterns cannot be matched in parallel using \"=\""
+
+ assert_raise CompileError, message, fn ->
+ expand(quote(do: <<foo>> = <<baz>> = bar()))
+ end
+
+ assert_raise CompileError, message, fn ->
+ expand(quote(do: <<foo>> = qux = <<baz>> = bar()))
+ end
+
+ assert_raise CompileError, message, fn ->
+ expand(quote(do: {<<foo>>} = {qux} = {<<baz>>} = bar()))
+ end
+
+ assert expand(quote(do: {:foo, <<foo>>} = {<<baz>>, :baz} = bar()))
+
+ # 2-element tuples are special cased
+ assert_raise CompileError, message, fn ->
+ expand(quote(do: {:foo, <<foo>>} = {:foo, <<baz>>} = bar()))
+ end
+
+ assert_raise CompileError, message, fn ->
+ expand(quote(do: %{foo: <<foo>>} = %{baz: <<qux>>, foo: <<baz>>} = bar()))
+ end
+
+ assert expand(quote(do: %{foo: <<foo>>} = %{baz: <<baz>>} = bar()))
+
+ assert_raise CompileError, message, fn ->
+ expand(quote(do: %_{foo: <<foo>>} = %_{foo: <<baz>>} = bar()))
+ end
+
+ assert expand(quote(do: %_{foo: <<foo>>} = %_{baz: <<baz>>} = bar()))
+
+ assert_raise CompileError, message, fn ->
+ expand(quote(do: %_{foo: <<foo>>} = %{foo: <<baz>>} = bar()))
+ end
+
+ assert expand(quote(do: %_{foo: <<foo>>} = %{baz: <<baz>>} = bar()))
+
+ assert_raise CompileError, message, fn ->
+ code =
+ quote do
+ case bar() do
+ <<foo>> = <<baz>> -> nil
+ end
+ end
+
+ expand(code)
+ end
+
+ assert_raise CompileError, message, fn ->
+ code =
+ quote do
+ case bar() do
+ <<foo>> = qux = <<baz>> -> nil
+ end
+ end
+
+ expand(code)
+ end
+
+ assert_raise CompileError, message, fn ->
+ code =
+ quote do
+ case bar() do
+ [<<foo>>] = [<<baz>>] -> nil
+ end
+ end
+
+ expand(code)
+ end
+ end
+
test "nested match" do
assert expand(quote(do: <<foo = bar>>)) |> clean_meta([:alignment]) ==
quote(do: <<foo = bar()::integer()>>)