Skip to content

Commit 09d4356

Browse files
authored
Fix edge case in normalizer for keyword args (#13924)
Closes #13895. Closes #13916.
1 parent f39d85d commit 09d4356

File tree

2 files changed

+52
-7
lines changed

2 files changed

+52
-7
lines changed

lib/elixir/lib/code/normalizer.ex

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -347,14 +347,14 @@ defmodule Code.Normalizer do
347347
args = normalize_args(args, %{state | parent_meta: meta})
348348
{form, meta, args}
349349

350-
Keyword.has_key?(meta, :do) or match?([{{:__block__, _, [:do]}, _} | _], last) ->
350+
Keyword.has_key?(meta, :do) ->
351351
# def foo do :ok end
352352
# def foo, do: :ok
353353
normalize_kw_blocks(form, meta, args, state)
354354

355355
match?([{:do, _} | _], last) and Keyword.keyword?(last) ->
356356
# Non normalized kw blocks
357-
line = state.parent_meta[:line]
357+
line = state.parent_meta[:line] || meta[:line]
358358
meta = meta ++ [do: [line: line], end: [line: line]]
359359
normalize_kw_blocks(form, meta, args, state)
360360

@@ -364,11 +364,20 @@ defmodule Code.Normalizer do
364364

365365
last_args =
366366
case last_arg do
367-
{:__block__, _, [[{{:__block__, key_meta, _}, _} | _]] = last_args} ->
368-
if key_meta[:format] == :keyword do
369-
last_args
370-
else
371-
[last_arg]
367+
{:__block__, _meta, [[{{:__block__, key_meta, _}, _} | _] = keyword]} ->
368+
cond do
369+
key_meta[:format] == :keyword ->
370+
[keyword]
371+
372+
block_keyword?(keyword) ->
373+
[
374+
Enum.map(keyword, fn {{:__block__, meta, args}, value} ->
375+
{{:__block__, [format: :keyword] ++ meta, args}, value}
376+
end)
377+
]
378+
379+
true ->
380+
[last_arg]
372381
end
373382

374383
[] ->
@@ -382,6 +391,12 @@ defmodule Code.Normalizer do
382391
end
383392
end
384393

394+
defp block_keyword?([{{:__block__, _, [key]}, _val} | tail]) when is_atom(key),
395+
do: block_keyword?(tail)
396+
397+
defp block_keyword?([]), do: true
398+
defp block_keyword?(_), do: false
399+
385400
defp allow_keyword?(:when, 2), do: true
386401
defp allow_keyword?(:{}, _), do: false
387402
defp allow_keyword?(op, arity), do: not is_atom(op) or not Macro.operator?(op, arity)

lib/elixir/test/elixir/code_normalizer/quoted_ast_test.exs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,36 @@ defmodule Code.Normalizer.QuotedASTTest do
629629
assert quoted_to_string(quote(do: foo |> [bar: :baz])) == "foo |> [bar: :baz]"
630630
end
631631

632+
test "keyword arg with cursor" do
633+
input = "def foo, do: :bar, __cursor__()"
634+
expected = "def foo, [{:do, :bar}, __cursor__()]"
635+
636+
ast = Code.string_to_quoted!(input)
637+
assert quoted_to_string(ast) == expected
638+
639+
encoder = &{:ok, {:__block__, &2, [&1]}}
640+
ast = Code.string_to_quoted!(input, literal_encoder: encoder)
641+
assert quoted_to_string(ast) == expected
642+
643+
ast = Code.string_to_quoted!(input, token_metadata: true)
644+
assert quoted_to_string(ast) == expected
645+
646+
ast = Code.string_to_quoted!(input, literal_encoder: encoder, token_metadata: true)
647+
assert quoted_to_string(ast) == expected
648+
end
649+
650+
test "keyword arg with literal encoder and no metadata" do
651+
input = """
652+
foo(Bar) do
653+
:ok
654+
end
655+
"""
656+
657+
encoder = &{:ok, {:__block__, &2, [&1]}}
658+
ast = Code.string_to_quoted!(input, literal_encoder: encoder)
659+
assert quoted_to_string(ast) == "foo(Bar, do: :ok)"
660+
end
661+
632662
test "list in module attribute" do
633663
assert quoted_to_string(
634664
quote do

0 commit comments

Comments
 (0)