Skip to content

Commit 90d26e6

Browse files
committed
Support swapped args in remote :erlang module
1 parent b49e1f5 commit 90d26e6

File tree

2 files changed

+60
-30
lines changed

2 files changed

+60
-30
lines changed

lib/gradient/ast_specifier.ex

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -328,12 +328,9 @@ defmodule Gradient.AstSpecifier do
328328
def mapper({:call, anno, name, args}, tokens, opts) do
329329
# anno has correct line
330330
{:ok, _, anno, opts, _} = get_line(anno, opts)
331-
332331
name = remote_mapper(name)
333332

334-
{opts, args} = call_with_pipe_op(tokens, args, opts)
335-
336-
{args, tokens} = context_mapper_fold(args, tokens, opts)
333+
{args, tokens} = call_args_mapper(args, tokens, name, opts)
337334

338335
{:call, anno, name, args}
339336
|> pass_tokens(tokens)
@@ -388,8 +385,7 @@ defmodule Gradient.AstSpecifier do
388385
end
389386

390387
def mapper({type, anno, value}, tokens, opts)
391-
when type in [:atom, :char, :float, :integer, :string, :bin] do
392-
# TODO check what happened for :string
388+
when type in @lineless_forms do
393389
{:ok, line} = Keyword.fetch(opts, :line)
394390
anno = :erl_anno.set_line(line, anno)
395391
anno = :erl_anno.set_generated(Keyword.get(opts, :generated, false), anno)
@@ -594,6 +590,29 @@ defmodule Gradient.AstSpecifier do
594590
end
595591
end
596592

593+
@doc """
594+
Update location in call args with the support to the pipe operator.
595+
"""
596+
@spec call_args_mapper([abstract_expr()], tokens(), abstract_expr(), options()) ::
597+
{options, [abstract_expr]}
598+
def call_args_mapper(args, tokens, name, opts) do
599+
# Check whether the call is after |> operator. If true, the parent location is set to 0
600+
# and the first arg location is cleared (if this arg is a lineless form).
601+
# NOTE If the call is to function from :erlang module then the first arg is swapped
602+
# with the second one because in Erlang the data is mostly in the second place.
603+
with true <- is_pipe_op?(tokens, opts),
604+
swapped? <- is_call_to_erlang?(name),
605+
[fst_arg | tail_args] <- maybe_swap_args(swapped?, args),
606+
true <- is_lineless?(fst_arg) do
607+
{arg, tokens} = mapper(clear_location(fst_arg), tokens, Keyword.put(opts, :line, 0))
608+
{args, tokens} = context_mapper_fold(tail_args, tokens, opts)
609+
{maybe_swap_args(swapped?, [arg | args]), tokens}
610+
else
611+
_ ->
612+
context_mapper_fold(args, tokens, opts)
613+
end
614+
end
615+
597616
# Private Helpers
598617

599618
@spec match_token_to_form(token(), form()) :: boolean()
@@ -790,32 +809,22 @@ defmodule Gradient.AstSpecifier do
790809
{form, tokens}
791810
end
792811

793-
@spec call_with_pipe_op(tokens(), [abstract_expr()], options()) :: {options, [abstract_expr]}
794-
def call_with_pipe_op(tokens, args, opts) do
795-
# Check whether the call is after |> operator. If true, the parent location is set to 0
796-
# and the first arg location is cleared (if this arg is a lineless form).
797-
# Clearing the location is required only for Elixir 1.13 or newer because from this version
798-
# the missing locations are specified, unfortunately sometimes not precise enough.
799-
{:ok, line} = Keyword.fetch(opts, :line)
800-
801-
case {List.first(drop_tokens_to_line(tokens, line)), is_first_arg_lineless?(args)} do
802-
{{:arrow_op, _loc, :|>}, true} ->
803-
{Keyword.put(opts, :line, 0), clear_first_arg_location(args)}
804-
805-
_ ->
806-
{opts, args}
812+
defp is_pipe_op?(tokens, opts) do
813+
case List.first(drop_tokens_to_line(tokens, Keyword.fetch!(opts, :line))) do
814+
{:arrow_op, _, :|>} -> true
815+
_ -> false
807816
end
808817
end
809818

810-
def is_first_arg_lineless?([form | _]), do: is_lineless_form?(form)
811-
def is_first_arg_lineless?([]), do: false
819+
defp maybe_swap_args(true, [fst, snd | t]), do: [snd, fst | t]
820+
defp maybe_swap_args(_, args), do: args
812821

813-
def is_lineless_form?(form) do
814-
elem(form, 0) in @lineless_forms
815-
end
822+
defp is_call_to_erlang?({:remote, _, {:atom, _, :erlang}, _}), do: true
823+
defp is_call_to_erlang?(_), do: false
816824

817-
def clear_first_arg_location([form | t]), do: [clear_location(form) | t]
818-
def clear_first_arg_location([]), do: []
825+
defp is_lineless?(expr) do
826+
elem(expr, 0) in @lineless_forms
827+
end
819828

820-
def clear_location(form), do: put_elem(form, 1, :erl_anno.set_line(0, elem(form, 1)))
829+
defp clear_location(form), do: put_elem(form, 1, :erl_anno.set_line(0, elem(form, 1)))
821830
end

test/support/ast_data.ex

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,30 @@ defmodule Gradient.AstData do
4848
]}}
4949
end
5050

51-
@spec ast_data() :: [{Types.abstract_expr(), Types.tokens(), Types.options()}]
51+
defp pipe_with_fun_converted_to_erl_equivalent do
52+
{__ENV__.function,
53+
{__ENV__.line,
54+
elixir_to_ast do
55+
:ok
56+
|> elem(0)
57+
end, __ENV__.line},
58+
{:call, 56, {:remote, 56, {:atom, 56, :erlang}, {:atom, 56, :element}},
59+
[{:integer, 56, 1}, {:atom, 55, :ok}]}}
60+
end
61+
62+
defp example do
63+
{__ENV__.function,
64+
{__ENV__.line,
65+
elixir_to_ast do
66+
:ok
67+
end, __ENV__.line}, _expected = {}}
68+
end
69+
70+
@spec ast_data() :: [
71+
{atom(), {Types.abstract_expr(), Types.tokens(), Types.options()}, tuple()}
72+
]
5273
def ast_data do
53-
[pipe()]
74+
[pipe(), pipe_with_fun_converted_to_erl_equivalent()]
5475
|> Enum.map(fn {{name, _}, {start_line, ast, end_line}, expected} ->
5576
tokens = Gradient.Tokens.drop_tokens_to_line(@tokens, start_line)
5677
{name, {ast, tokens, [line: start_line, end_line: end_line]}, expected}

0 commit comments

Comments
 (0)