Skip to content

Commit ca96bca

Browse files
committed
perf: prefer recursion over Keyword.has_key?
Perhaps surprisingly, using tail recursion is generally faster than Keyword.has_key? by about 10%. In this case though the most important thing is that we iterate over the list only once, instead of 2-3 times. The benchmark shows 7-8% improvement on parsing files.
1 parent c1a4f1b commit ca96bca

1 file changed

Lines changed: 30 additions & 9 deletions

File tree

lib/spitfire.ex

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -957,12 +957,11 @@ defmodule Spitfire do
957957
end
958958
end
959959

960-
defp invalid_assoc_call_meta?(meta) when is_list(meta) do
961-
not Keyword.has_key?(meta, :parens) and
962-
not Keyword.has_key?(meta, :closing) and
963-
not Keyword.has_key?(meta, :delimiter) and
964-
not Keyword.has_key?(meta, :do)
965-
end
960+
defp invalid_assoc_call_meta?([]), do: true
961+
962+
defp invalid_assoc_call_meta?([{key, _value} | _meta]) when key in [:parens, :closing, :delimiter, :do], do: false
963+
964+
defp invalid_assoc_call_meta?([_entry | meta]), do: invalid_assoc_call_meta?(meta)
966965

967966
defp invalid_assoc_key_in_map?({name, meta, args}) when is_atom(name) and is_list(meta) and is_list(args) do
968967
arity = length(args)
@@ -1034,14 +1033,28 @@ defmodule Spitfire do
10341033
defp unparenthesized_do_end_block?(ast) do
10351034
case ast do
10361035
{_, meta, _} when is_list(meta) ->
1037-
Keyword.has_key?(meta, :do) && Keyword.has_key?(meta, :end) &&
1038-
not Keyword.has_key?(meta, :parens)
1036+
meta_has_do_end_without_parens?(meta)
10391037

10401038
_ ->
10411039
false
10421040
end
10431041
end
10441042

1043+
# using recursion here to pass the list only once
1044+
defp meta_has_do_end_without_parens?(meta), do: meta_has_do_end_without_parens?(meta, false, false)
1045+
1046+
defp meta_has_do_end_without_parens?([], has_do, has_end), do: has_do and has_end
1047+
defp meta_has_do_end_without_parens?([{:parens, _value} | _meta], _has_do, _has_end), do: false
1048+
1049+
defp meta_has_do_end_without_parens?([{:do, _value} | meta], _has_do, has_end),
1050+
do: meta_has_do_end_without_parens?(meta, true, has_end)
1051+
1052+
defp meta_has_do_end_without_parens?([{:end, _value} | meta], has_do, _has_end),
1053+
do: meta_has_do_end_without_parens?(meta, has_do, true)
1054+
1055+
defp meta_has_do_end_without_parens?([_entry | meta], has_do, has_end),
1056+
do: meta_has_do_end_without_parens?(meta, has_do, has_end)
1057+
10451058
# An expression is "unmatched" if it contains an unparenthesized do-end block
10461059
# anywhere in its AST. Binary operators with an unmatched operand produce
10471060
# unmatched expressions.
@@ -1745,11 +1758,19 @@ defmodule Spitfire do
17451758
end
17461759

17471760
defp has_do_end_block?({_, meta, _}) when is_list(meta) do
1748-
Keyword.has_key?(meta, :do) and Keyword.has_key?(meta, :end)
1761+
meta_has_do_end?(meta)
17491762
end
17501763

17511764
defp has_do_end_block?(_), do: false
17521765

1766+
# using recursion here to pass the list only once
1767+
defp meta_has_do_end?(meta), do: meta_has_do_end?(meta, false, false)
1768+
1769+
defp meta_has_do_end?([], has_do, has_end), do: has_do and has_end
1770+
defp meta_has_do_end?([{:do, _value} | meta], _has_do, has_end), do: meta_has_do_end?(meta, true, has_end)
1771+
defp meta_has_do_end?([{:end, _value} | meta], has_do, _has_end), do: meta_has_do_end?(meta, has_do, true)
1772+
defp meta_has_do_end?([_entry | meta], has_do, has_end), do: meta_has_do_end?(meta, has_do, has_end)
1773+
17531774
@binary_op_types [
17541775
:and_op,
17551776
:or_op,

0 commit comments

Comments
 (0)