Skip to content

Commit 39acd23

Browse files
authored
Optimize Keyword.validate/2 by adding fast-track list merging (#15499)
1 parent 1ac9007 commit 39acd23

2 files changed

Lines changed: 48 additions & 21 deletions

File tree

lib/elixir/lib/keyword.ex

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -269,39 +269,61 @@ defmodule Keyword do
269269
@doc since: "1.13.0"
270270
@spec validate(keyword(), values :: [atom() | {atom(), term()}]) ::
271271
{:ok, keyword()} | {:error, [atom]}
272+
def validate([], values) when is_list(values), do: {:ok, move_pairs!(values, [])}
273+
272274
def validate(keyword, values) when is_list(keyword) and is_list(values) do
273-
validate(keyword, values, [], keyword, [])
275+
validate_merge(keyword, values, [], keyword)
274276
end
275277

276-
defp validate([{key, _} | keyword], values1, values2, original, bad_keys) when is_atom(key) do
277-
case find_key!(key, values1, values2) do
278-
{values1, values2} ->
279-
validate(keyword, values1, values2, original, bad_keys)
280-
281-
:error ->
282-
case find_key!(key, values2, values1) do
283-
{values1, values2} ->
284-
validate(keyword, values1, values2, original, bad_keys)
278+
defp validate_merge([], values, values_pre, original),
279+
do: {:ok, move_pairs!(values, move_pairs!(values_pre, original))}
285280

286-
:error ->
287-
validate(keyword, values1, values2, original, [key | bad_keys])
288-
end
281+
defp validate_merge([{key, _} = pair | keyword], [head | tail], values_pre, original)
282+
when is_atom(key) do
283+
case head do
284+
^key -> validate_merge(keyword, tail, values_pre, original)
285+
{^key, _} -> validate_merge(keyword, tail, values_pre, original)
286+
_ -> validate_merge([pair | keyword], tail, [head | values_pre], original)
289287
end
290288
end
291289

292-
defp validate([], values1, values2, original, []) do
293-
{:ok, move_pairs!(values1, move_pairs!(values2, original))}
294-
end
290+
defp validate_merge([{key, _} | keyword], [], values_pre, original) when is_atom(key) do
291+
case find_key!(key, values_pre, []) do
292+
{new_values, new_values_pre} ->
293+
validate_merge(keyword, new_values, new_values_pre, original)
295294

296-
defp validate([], _values1, _values2, _original, bad_keys) do
297-
{:error, bad_keys}
295+
:error ->
296+
validate_fallback(keyword, values_pre, [key])
297+
end
298298
end
299299

300-
defp validate([pair | _], _values1, _values2, _original, []) do
301-
raise ArgumentError,
302-
"expected a keyword list as first argument, got invalid entry: #{inspect(pair)}"
300+
defp validate_merge([pair | _], _, _, _),
301+
do:
302+
raise(
303+
ArgumentError,
304+
"expected a keyword list as first argument, got invalid entry: #{inspect(pair)}"
305+
)
306+
307+
defp validate_fallback([{key, _} | keyword], values, bad_keys)
308+
when is_atom(key) do
309+
case find_key!(key, values, []) do
310+
{rest, acc} ->
311+
validate_fallback(keyword, rest ++ acc, bad_keys)
312+
313+
:error ->
314+
validate_fallback(keyword, values, [key | bad_keys])
315+
end
303316
end
304317

318+
defp validate_fallback([], _, bad), do: {:error, bad}
319+
320+
defp validate_fallback([p | _], _, _),
321+
do:
322+
raise(
323+
ArgumentError,
324+
"expected a keyword list as first argument, got invalid entry: #{inspect(p)}"
325+
)
326+
305327
defp find_key!(key, [key | rest], acc), do: {rest, acc}
306328
defp find_key!(key, [{key, _} | rest], acc), do: {rest, acc}
307329
defp find_key!(key, [head | tail], acc), do: find_key!(key, tail, [head | acc])

lib/elixir/test/elixir/keyword_test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ defmodule KeywordTest do
230230
fn -> Keyword.validate([three: 3], [:three, 3, :two]) end
231231
end
232232

233+
test "validate/2 returns invalid and duplicate keys after matching allowed keys" do
234+
assert Keyword.validate([one: 1, two: 2, three: 3, one: 4], [:one, :two]) ==
235+
{:error, [:one, :three]}
236+
end
237+
233238
test "split_with/2" do
234239
assert Keyword.split_with([], fn {_k, v} -> rem(v, 2) == 0 end) == {[], []}
235240

0 commit comments

Comments
 (0)