From 10e44deae6da71026de099c198b6d721e49c9fef Mon Sep 17 00:00:00 2001 From: preciz Date: Tue, 16 Jun 2026 11:03:07 +0200 Subject: [PATCH 1/3] Optimize Keyword.validate/2 by adding fast-track list merging We optimize Keyword.validate/2 by introducing a fast-path validate_merge/4 that checks matching keys from the start of the list to avoid scanning the entire remainder where possible. For mismatched keys, it falls back to validate_fallback/3. This improves performance on all input sizes and reduces memory usage. Assisted-by: Antigravity:gemini-3.5-flash --- lib/elixir/lib/keyword.ex | 64 ++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/lib/elixir/lib/keyword.ex b/lib/elixir/lib/keyword.ex index 984838066c5..b634033b29e 100644 --- a/lib/elixir/lib/keyword.ex +++ b/lib/elixir/lib/keyword.ex @@ -269,39 +269,61 @@ defmodule Keyword do @doc since: "1.13.0" @spec validate(keyword(), values :: [atom() | {atom(), term()}]) :: {:ok, keyword()} | {:error, [atom]} + def validate([], values) when is_list(values), do: {:ok, move_pairs!(values, [])} + def validate(keyword, values) when is_list(keyword) and is_list(values) do - validate(keyword, values, [], keyword, []) + validate_merge(keyword, values, [], keyword) end - defp validate([{key, _} | keyword], values1, values2, original, bad_keys) when is_atom(key) do - case find_key!(key, values1, values2) do - {values1, values2} -> - validate(keyword, values1, values2, original, bad_keys) - - :error -> - case find_key!(key, values2, values1) do - {values1, values2} -> - validate(keyword, values1, values2, original, bad_keys) + defp validate_merge([], values, values_pre, original), + do: {:ok, move_pairs!(values, move_pairs!(values_pre, original))} - :error -> - validate(keyword, values1, values2, original, [key | bad_keys]) - end + defp validate_merge([{key, _} = pair | keyword], [head | tail], values_pre, original) + when is_atom(key) do + case head do + ^key -> validate_merge(keyword, tail, values_pre, original) + {^key, _} -> validate_merge(keyword, tail, values_pre, original) + _ -> validate_merge([pair | keyword], tail, [head | values_pre], original) end end - defp validate([], values1, values2, original, []) do - {:ok, move_pairs!(values1, move_pairs!(values2, original))} - end + defp validate_merge([{key, _} | keyword], [], values_pre, original) when is_atom(key) do + case find_key!(key, values_pre, []) do + {new_values, new_values_pre} -> + validate_merge(keyword, new_values, new_values_pre, original) - defp validate([], _values1, _values2, _original, bad_keys) do - {:error, bad_keys} + :error -> + validate_fallback(keyword, values_pre, [key]) + end end - defp validate([pair | _], _values1, _values2, _original, []) do - raise ArgumentError, - "expected a keyword list as first argument, got invalid entry: #{inspect(pair)}" + defp validate_merge([pair | _], _, _, _), + do: + raise( + ArgumentError, + "expected a keyword list as first argument, got invalid entry: #{inspect(pair)}" + ) + + defp validate_fallback([{key, _} | keyword], values, bad_keys) + when is_atom(key) do + case find_key!(key, values, []) do + {rest, acc} -> + validate_fallback(keyword, rest ++ acc, bad_keys) + + :error -> + validate_fallback(keyword, values, [key | bad_keys]) + end end + defp validate_fallback([], _, bad), do: {:error, bad} + + defp validate_fallback([p | _], _, _), + do: + raise( + ArgumentError, + "expected a keyword list as first argument, got invalid entry: #{inspect(p)}" + ) + defp find_key!(key, [key | rest], acc), do: {rest, acc} defp find_key!(key, [{key, _} | rest], acc), do: {rest, acc} defp find_key!(key, [head | tail], acc), do: find_key!(key, tail, [head | acc]) From 3fd06a164866618458eb3a701cb28dc8e9b5bd82 Mon Sep 17 00:00:00 2001 From: preciz Date: Wed, 17 Jun 2026 08:31:17 +0200 Subject: [PATCH 2/3] Test Keyword.validate edge cases --- lib/elixir/test/elixir/keyword_test.exs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/elixir/test/elixir/keyword_test.exs b/lib/elixir/test/elixir/keyword_test.exs index 2a133886cf3..48e2d90a5c6 100644 --- a/lib/elixir/test/elixir/keyword_test.exs +++ b/lib/elixir/test/elixir/keyword_test.exs @@ -230,6 +230,20 @@ defmodule KeywordTest do fn -> Keyword.validate([three: 3], [:three, 3, :two]) end end + test "validate/2 validates keys in allowed order" do + assert Keyword.validate([one: 10, three: 30], one: 1, two: 2, three: 3, four: 4) == + {:ok, [four: 4, two: 2, one: 10, three: 30]} + end + + test "validate/2 validates keys after skipped allowed keys" do + assert Keyword.validate([three: 30, one: 10], one: 1, two: 2, three: 3, four: 4) == + {:ok, [four: 4, two: 2, three: 30, one: 10]} + end + + test "validate/2 returns duplicate keys after matching allowed keys" do + assert Keyword.validate([one: 1, two: 2, one: 3], [:one, :two]) == {:error, [:one]} + end + test "split_with/2" do assert Keyword.split_with([], fn {_k, v} -> rem(v, 2) == 0 end) == {[], []} From d804c232bbdf101c9fe3c0f858510b786bf20923 Mon Sep 17 00:00:00 2001 From: preciz Date: Wed, 17 Jun 2026 09:13:44 +0200 Subject: [PATCH 3/3] Clean up tests --- lib/elixir/test/elixir/keyword_test.exs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lib/elixir/test/elixir/keyword_test.exs b/lib/elixir/test/elixir/keyword_test.exs index 48e2d90a5c6..8117e9053f5 100644 --- a/lib/elixir/test/elixir/keyword_test.exs +++ b/lib/elixir/test/elixir/keyword_test.exs @@ -230,18 +230,9 @@ defmodule KeywordTest do fn -> Keyword.validate([three: 3], [:three, 3, :two]) end end - test "validate/2 validates keys in allowed order" do - assert Keyword.validate([one: 10, three: 30], one: 1, two: 2, three: 3, four: 4) == - {:ok, [four: 4, two: 2, one: 10, three: 30]} - end - - test "validate/2 validates keys after skipped allowed keys" do - assert Keyword.validate([three: 30, one: 10], one: 1, two: 2, three: 3, four: 4) == - {:ok, [four: 4, two: 2, three: 30, one: 10]} - end - - test "validate/2 returns duplicate keys after matching allowed keys" do - assert Keyword.validate([one: 1, two: 2, one: 3], [:one, :two]) == {:error, [:one]} + test "validate/2 returns invalid and duplicate keys after matching allowed keys" do + assert Keyword.validate([one: 1, two: 2, three: 3, one: 4], [:one, :two]) == + {:error, [:one, :three]} end test "split_with/2" do