diff options
author | Aleksei Matiushkin <aleksei.matiushkin@kantox.com> | 2021-12-03 20:24:09 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-03 20:24:09 +0100 |
commit | 8c11ba5cd7cd120f13b7da6e723577cb0c5c439d (patch) | |
tree | b312b2e69f0c50f572080db7bd8bcf26bc5ad9bf | |
parent | 4995e0671309b2b70351cbb7f43408f12d913303 (diff) | |
download | elixir-8c11ba5cd7cd120f13b7da6e723577cb0c5c439d.tar.gz |
Disallow shorthand pipe after matches (#11433)
Closes #11418.
-rw-r--r-- | lib/iex/lib/iex.ex | 10 | ||||
-rw-r--r-- | lib/iex/lib/iex/evaluator.ex | 44 | ||||
-rw-r--r-- | lib/iex/lib/iex/server.ex | 10 | ||||
-rw-r--r-- | lib/iex/test/iex/helpers_test.exs | 11 |
4 files changed, 61 insertions, 14 deletions
diff --git a/lib/iex/lib/iex.ex b/lib/iex/lib/iex.ex index 47771c2b6..f49131809 100644 --- a/lib/iex/lib/iex.ex +++ b/lib/iex/lib/iex.ex @@ -136,6 +136,16 @@ defmodule IEx do iex(1)> |> List.flatten() ** (RuntimeError) v(-1) is out of bounds + If the previous expression was a match operation, the pipe operator will also + fail, to prevent an unsolicited break of the match: + + iex(1)> x = 42 + iex(2)> |> IO.puts() + ** (SyntaxError) iex:2:1: pipe shorthand is not allowed immediately after a match expression in IEx. To make it work, surround the whole pipeline with parentheses ('|>') + | + 2 | |> IO.puts() + | ^ + Note however the above does not work for `+/2` and `-/2`, as they are ambiguous with the unary `+/1` and `-/1`: diff --git a/lib/iex/lib/iex/evaluator.ex b/lib/iex/lib/iex/evaluator.ex index 9c8d36ae8..cf4dcc0f8 100644 --- a/lib/iex/lib/iex/evaluator.ex +++ b/lib/iex/lib/iex/evaluator.ex @@ -62,13 +62,15 @@ defmodule IEx.Evaluator do [:three_op, :concat_op, :mult_op] @doc false - def parse(input, opts, buffer) + def parse(input, opts, parser_state) - def parse(@break_trigger, _opts, "") do - {:incomplete, ""} + def parse(input, opts, ""), do: parse(input, opts, {"", :other}) + + def parse(@break_trigger, _opts, {"", _} = parser_state) do + {:incomplete, parser_state} end - def parse(@break_trigger, opts, _buffer) do + def parse(@break_trigger, opts, _parser_state) do :elixir_errors.parse_error( [line: opts[:line]], opts[:file], @@ -78,7 +80,7 @@ defmodule IEx.Evaluator do ) end - def parse(input, opts, buffer) do + def parse(input, opts, {buffer, last_op}) do input = buffer <> input file = Keyword.get(opts, :file, "nofile") line = Keyword.get(opts, :line, 1) @@ -86,16 +88,24 @@ defmodule IEx.Evaluator do charlist = String.to_charlist(input) result = - with {:ok, tokens} <- :elixir.string_to_tokens(charlist, line, column, file, opts) do - :elixir.tokens_to_quoted(adjust_operator(tokens, line, column, file, opts), file, opts) + with {:ok, tokens} <- :elixir.string_to_tokens(charlist, line, column, file, opts), + {:ok, adjusted_tokens} <- adjust_operator(tokens, line, column, file, opts, last_op), + {:ok, forms} <- :elixir.tokens_to_quoted(adjusted_tokens, file, opts) do + last_op = + case forms do + {:=, _, [_, _]} -> :match + _ -> :other + end + + {:ok, forms, last_op} end case result do - {:ok, forms} -> - {:ok, forms, ""} + {:ok, forms, last_op} -> + {:ok, forms, {"", last_op}} {:error, {_, _, ""}} -> - {:incomplete, input} + {:incomplete, {input, last_op}} {:error, {location, error, token}} -> :elixir_errors.parse_error( @@ -108,13 +118,21 @@ defmodule IEx.Evaluator do end end - defp adjust_operator([{op_type, _, _} | _] = tokens, line, column, file, opts) + defp adjust_operator([{op_type, _, token} | _] = _tokens, line, column, _file, _opts, :match) + when op_type in @op_tokens, + do: + {:error, + {[line: line, column: column], + "pipe shorthand is not allowed immediately after a match expression in IEx. To make it work, surround the whole pipeline with parentheses ", + "'#{token}'"}} + + defp adjust_operator([{op_type, _, _} | _] = tokens, line, column, file, opts, _last_op) when op_type in @op_tokens do {:ok, prefix} = :elixir.string_to_tokens('v(-1)', line, column, file, opts) - prefix ++ tokens + {:ok, prefix ++ tokens} end - defp adjust_operator(tokens, _line, _column, _file, _opts), do: tokens + defp adjust_operator(tokens, _line, _column, _file, _opts, _last_op), do: {:ok, tokens} @doc """ Gets a value out of the binding, using the provided diff --git a/lib/iex/lib/iex/server.ex b/lib/iex/lib/iex/server.ex index 5e4fe2c1a..b44ac8444 100644 --- a/lib/iex/lib/iex/server.ex +++ b/lib/iex/lib/iex/server.ex @@ -9,7 +9,15 @@ defmodule IEx.State do evaluator_options: [], previous_state: nil - @type t :: %__MODULE__{} + @type t :: %{ + __struct__: __MODULE__, + parser_state: binary(), + counter: pos_integer(), + prefix: binary(), + on_eof: :stop_evaluator | :halt, + evaluator_options: keyword(), + previous_state: nil | t() + } end defmodule IEx.Server do diff --git a/lib/iex/test/iex/helpers_test.exs b/lib/iex/test/iex/helpers_test.exs index ac8f52dab..e7af366ae 100644 --- a/lib/iex/test/iex/helpers_test.exs +++ b/lib/iex/test/iex/helpers_test.exs @@ -1127,6 +1127,17 @@ defmodule IEx.HelpersTest do assert capture_iex("[42]\n++ [24]\n|> IO.inspect(label: \"foo\")") =~ "foo: [42, 24]" assert capture_iex("|> IO.puts()") =~ "(RuntimeError) v(-1) is out of bounds" end + + test "raises if previous expression was a match" do + assert capture_iex("x = 42\n|> IO.puts()") =~ + "surround the whole pipeline with parentheses '|>'" + + assert capture_iex("%{x: x} = %{x: 42}\n|> IO.puts()") =~ + "surround the whole pipeline with parentheses '|>'" + + assert capture_iex("%{x: x} = map = %{x: 42}\n|> IO.puts()") =~ + "surround the whole pipeline with parentheses '|>'" + end end describe "c" do |