diff --git a/lib/elixir/lib/code/normalizer.ex b/lib/elixir/lib/code/normalizer.ex index 96ec5a826db..32189dfc972 100644 --- a/lib/elixir/lib/code/normalizer.ex +++ b/lib/elixir/lib/code/normalizer.ex @@ -347,14 +347,14 @@ defmodule Code.Normalizer do args = normalize_args(args, %{state | parent_meta: meta}) {form, meta, args} - Keyword.has_key?(meta, :do) or match?([{{:__block__, _, [:do]}, _} | _], last) -> + Keyword.has_key?(meta, :do) -> # def foo do :ok end # def foo, do: :ok normalize_kw_blocks(form, meta, args, state) match?([{:do, _} | _], last) and Keyword.keyword?(last) -> # Non normalized kw blocks - line = state.parent_meta[:line] + line = state.parent_meta[:line] || meta[:line] meta = meta ++ [do: [line: line], end: [line: line]] normalize_kw_blocks(form, meta, args, state) @@ -364,11 +364,20 @@ defmodule Code.Normalizer do last_args = case last_arg do - {:__block__, _, [[{{:__block__, key_meta, _}, _} | _]] = last_args} -> - if key_meta[:format] == :keyword do - last_args - else - [last_arg] + {:__block__, _meta, [[{{:__block__, key_meta, _}, _} | _] = keyword]} -> + cond do + key_meta[:format] == :keyword -> + [keyword] + + block_keyword?(keyword) -> + [ + Enum.map(keyword, fn {{:__block__, meta, args}, value} -> + {{:__block__, [format: :keyword] ++ meta, args}, value} + end) + ] + + true -> + [last_arg] end [] -> @@ -382,6 +391,12 @@ defmodule Code.Normalizer do end end + defp block_keyword?([{{:__block__, _, [key]}, _val} | tail]) when is_atom(key), + do: block_keyword?(tail) + + defp block_keyword?([]), do: true + defp block_keyword?(_), do: false + defp allow_keyword?(:when, 2), do: true defp allow_keyword?(:{}, _), do: false defp allow_keyword?(op, arity), do: not is_atom(op) or not Macro.operator?(op, arity) 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 cc41cf20eba..8b5877f799a 100644 --- a/lib/elixir/test/elixir/code_normalizer/quoted_ast_test.exs +++ b/lib/elixir/test/elixir/code_normalizer/quoted_ast_test.exs @@ -629,6 +629,36 @@ defmodule Code.Normalizer.QuotedASTTest do assert quoted_to_string(quote(do: foo |> [bar: :baz])) == "foo |> [bar: :baz]" end + test "keyword arg with cursor" do + input = "def foo, do: :bar, __cursor__()" + expected = "def foo, [{:do, :bar}, __cursor__()]" + + ast = Code.string_to_quoted!(input) + assert quoted_to_string(ast) == expected + + encoder = &{:ok, {:__block__, &2, [&1]}} + ast = Code.string_to_quoted!(input, literal_encoder: encoder) + assert quoted_to_string(ast) == expected + + ast = Code.string_to_quoted!(input, token_metadata: true) + assert quoted_to_string(ast) == expected + + ast = Code.string_to_quoted!(input, literal_encoder: encoder, token_metadata: true) + assert quoted_to_string(ast) == expected + end + + test "keyword arg with literal encoder and no metadata" do + input = """ + foo(Bar) do + :ok + end + """ + + encoder = &{:ok, {:__block__, &2, [&1]}} + ast = Code.string_to_quoted!(input, literal_encoder: encoder) + assert quoted_to_string(ast) == "foo(Bar, do: :ok)" + end + test "list in module attribute" do assert quoted_to_string( quote do