summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksei Matiushkin <aleksei.matiushkin@kantox.com>2021-12-03 20:24:09 +0100
committerGitHub <noreply@github.com>2021-12-03 20:24:09 +0100
commit8c11ba5cd7cd120f13b7da6e723577cb0c5c439d (patch)
treeb312b2e69f0c50f572080db7bd8bcf26bc5ad9bf
parent4995e0671309b2b70351cbb7f43408f12d913303 (diff)
downloadelixir-8c11ba5cd7cd120f13b7da6e723577cb0c5c439d.tar.gz
Disallow shorthand pipe after matches (#11433)
Closes #11418.
-rw-r--r--lib/iex/lib/iex.ex10
-rw-r--r--lib/iex/lib/iex/evaluator.ex44
-rw-r--r--lib/iex/lib/iex/server.ex10
-rw-r--r--lib/iex/test/iex/helpers_test.exs11
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