From 5dda474035e2a247de2e35e95bf4c5aa8b0ca65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 24 May 2021 09:18:42 +0200 Subject: [PATCH] More fixes for quoted_to_algebra/2 --- lib/elixir/lib/code/formatter.ex | 28 +++-- lib/elixir/lib/code/normalizer.ex | 108 ++---------------- .../code_normalizer/quoted_ast_test.exs | 21 ++++ 3 files changed, 47 insertions(+), 110 deletions(-) diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex index 3581174845..e1eef58f5f 100644 --- a/lib/elixir/lib/code/formatter.ex +++ b/lib/elixir/lib/code/formatter.ex @@ -191,7 +191,7 @@ defmodule Code.Formatter do end @doc """ - Converts the quoted expression into an algebra document + Converts the quoted expression into an algebra document. """ def to_algebra(quoted, opts \\ []) do comments = Keyword.get(opts, :comments, []) @@ -207,6 +207,16 @@ defmodule Code.Formatter do doc end + @doc """ + Checks if a function is a local without parens. + """ + def local_without_parens?(fun, arity, locals_without_parens \\ @locals_without_parens) do + arity > 0 and + Enum.any?(locals_without_parens, fn {key, val} -> + key == fun and (val == :* or val == arity) + end) + end + defp state(comments, opts) do force_do_end_blocks = Keyword.get(opts, :force_do_end_blocks, false) @@ -545,6 +555,11 @@ defmodule Code.Formatter do {doc, state} end + # #PID's and #Ref's may appear on regular AST + defp quoted_to_algebra(unknown, _context, state) do + {inspect(unknown), state} + end + ## Blocks defp block_to_algebra([{:->, _, _} | _] = type_fun, min_line, max_line, state) do @@ -1028,7 +1043,7 @@ defmodule Code.Formatter do skip_parens = cond do meta?(meta, :closing) -> :skip_if_only_do_end - local_without_parens?(fun, args, state) -> :skip_unless_many_args + local_without_parens?(fun, length(args), state.locals_without_parens) -> :skip_unless_many_args true -> :skip_if_do_end end @@ -1208,15 +1223,6 @@ defmodule Code.Formatter do end end - defp local_without_parens?(fun, args, %{locals_without_parens: locals_without_parens}) do - length = length(args) - - length > 0 and - Enum.any?(locals_without_parens, fn {key, val} -> - key == fun and (val == :* or val == length) - end) - end - defp no_generators?(args) do not Enum.any?(args, &match?({:<-, _, [_, _]}, &1)) end diff --git a/lib/elixir/lib/code/normalizer.ex b/lib/elixir/lib/code/normalizer.ex index d21d813f46..601cef76b7 100644 --- a/lib/elixir/lib/code/normalizer.ex +++ b/lib/elixir/lib/code/normalizer.ex @@ -1,94 +1,6 @@ defmodule Code.Normalizer do @moduledoc false - # Operators that are logical cannot be mixed without parens - @required_parens_forms [:||, :|||, :or, :&&, :&&&, :and, :{}] - - @locals_without_parens [ - # Special forms - alias: 1, - alias: 2, - case: 2, - cond: 1, - for: :*, - import: 1, - import: 2, - quote: 1, - quote: 2, - receive: 1, - require: 1, - require: 2, - try: 1, - with: :*, - - # Kernel - def: 1, - def: 2, - defp: 1, - defp: 2, - defguard: 1, - defguardp: 1, - defmacro: 1, - defmacro: 2, - defmacrop: 1, - defmacrop: 2, - defmodule: 2, - defdelegate: 2, - defexception: 1, - defoverridable: 1, - defstruct: 1, - destructure: 2, - raise: 1, - raise: 2, - reraise: 2, - reraise: 3, - if: 2, - unless: 2, - use: 1, - use: 2, - - # Stdlib, - defrecord: 2, - defrecord: 3, - defrecordp: 2, - defrecordp: 3, - - # Testing - assert: 1, - assert: 2, - assert_in_delta: 3, - assert_in_delta: 4, - assert_raise: 2, - assert_raise: 3, - assert_receive: 1, - assert_receive: 2, - assert_receive: 3, - assert_received: 1, - assert_received: 2, - doctest: 1, - doctest: 2, - refute: 1, - refute: 2, - refute_in_delta: 3, - refute_in_delta: 4, - refute_receive: 1, - refute_receive: 2, - refute_receive: 3, - refute_received: 1, - refute_received: 2, - setup: 1, - setup: 2, - setup_all: 1, - setup_all: 2, - test: 1, - test: 2, - - # Mix config - config: 2, - config: 3, - import_config: 1 - ] - defguard is_literal(x) when is_integer(x) or is_float(x) or @@ -359,6 +271,7 @@ defmodule Code.Normalizer do defp normalize_call({form, meta, args}, state) do meta = patch_meta_line(meta, state.parent_meta) + arity = length(args) # Only normalize the form if it's a qualified call form = @@ -369,7 +282,7 @@ defmodule Code.Normalizer do end meta = - if is_nil(meta[:no_parens]) and not local_without_parens?(form, args) do + if is_nil(meta[:no_parens]) and not Code.Formatter.local_without_parens?(form, arity) do [closing: [line: meta[:line]]] ++ meta else meta @@ -388,7 +301,7 @@ defmodule Code.Normalizer do normalize_kw_blocks(form, meta, args, state) - form not in @required_parens_forms -> + allow_keyword?(form, arity) -> args = normalize_args(args, %{state | parent_meta: state.parent_meta}) {last_arg, leading_args} = List.pop_at(args, -1, []) @@ -417,6 +330,12 @@ defmodule Code.Normalizer do end end + defp allow_keyword?(:when, 2), do: true + defp allow_keyword?(:{}, _), do: false + defp allow_keyword?(op, 1), do: Code.Identifier.unary_op(op) == :error + defp allow_keyword?(op, 2), do: Code.Identifier.binary_op(op) == :error + defp allow_keyword?(_, _), do: true + defp normalize_bitstring({:<<>>, meta, parts} = quoted, state, escape_interpolation \\ false) do meta = patch_meta_line(meta, state.parent_meta) @@ -581,13 +500,4 @@ defmodule Code.Normalizer do defp interpolated?(_) do false end - - defp local_without_parens?(fun, args) do - length = length(args) - - length > 0 and - Enum.any?(@locals_without_parens, fn {key, val} -> - key == fun and (val == :* or val == length) - end) - end end diff --git a/lib/elixir/test/elixir/code_normalizer/quoted_ast_test.exs b/lib/elixir/test/elixir/code_normalizer/quoted_ast_test.exs index adde7fe22e..fba2f4f9f8 100644 --- a/lib/elixir/test/elixir/code_normalizer/quoted_ast_test.exs +++ b/lib/elixir/test/elixir/code_normalizer/quoted_ast_test.exs @@ -209,6 +209,20 @@ defmodule Code.Normalizer.QuotedASTTest do assert quoted_to_string(quoted) <> "\n" == expected end + test "case if else" do + expected = """ + case (if foo do + bar + else + baz + end) do + end + """ + + assert quoted_to_string(quote(do: case if(foo, do: bar, else: baz) do end)) <> "\n" == expected + end + + test "try" do quoted = quote do @@ -325,6 +339,8 @@ defmodule Code.Normalizer.QuotedASTTest do assert quoted_to_string(quote(do: (x when y -> z))) == "(x when y -> z)" assert quoted_to_string(quote(do: (x, y when z -> w))) == "(x, y when z -> w)" assert quoted_to_string(quote(do: (x, y when z -> w))) == "(x, y when z -> w)" + assert quoted_to_string(quote(do: (x, y when z -> w))) == "(x, y when z -> w)" + assert quoted_to_string(quote(do: (x when y: z))) == "x when y: z" end test "nested" do @@ -456,6 +472,10 @@ defmodule Code.Normalizer.QuotedASTTest do assert quoted_to_string(quote(do: "#{"abc"}")) == ~S/"#{"abc"}"/ end + test "catch-all" do + assert quoted_to_string(quote do: {unquote(self())}) == "{#{inspect(self())}}" + end + test "last arg keyword list" do assert quoted_to_string(quote(do: foo([]))) == "foo([])" assert quoted_to_string(quote(do: foo(x: y))) == "foo(x: y)" @@ -468,6 +488,7 @@ defmodule Code.Normalizer.QuotedASTTest do assert quoted_to_string(quote(do: {x, a: b})) == "{x, [a: b]}" assert quoted_to_string(quote(do: foo(else: a))) == "foo(else: a)" assert quoted_to_string(quote(do: foo(catch: a))) == "foo(catch: a)" + assert quoted_to_string(quote(do: foo |> [bar: :baz])) == "foo |> [bar: :baz]" end end