diff options
author | José Valim <jose.valim@gmail.com> | 2018-12-08 10:12:59 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-08 10:12:59 +0100 |
commit | 0cc21e36b26f4d9446b786465bbcfc2d1b998051 (patch) | |
tree | fd0d7a032b44ecaaeccf155501fcbd1d34b94569 | |
parent | 5f3e1217bfe5263b3bd4c6ccca61f986e9d038e0 (diff) | |
download | elixir-0cc21e36b26f4d9446b786465bbcfc2d1b998051.tar.gz |
Check for function arity in guards for aggregates in Enum (#8487)
In the past, we had decided to not use is_function/1 or
is_function/2 in guards in Enum because the underlying
BadFunctionError was clearer than a FunctionClauseError.
However, today this is no longer necessarily true, due
to exception blaming that is capable of diffing the clauses
in a FunctionClauseError.
Furthermore, not checking for the argument can be an issue
on arguments that are rarely invoked, such as the empty
clause default function passed to Enum.min/2 friends, which
makes some mistakes impossible to catch. For example, someone
may write:
Enum.min(non_empty_list, &elem(&1, 1))
The user intent here was actually to call `Enum.min_by/2`,
but without the guard, everything appears to work and the
error can go undetected.
Therefore this commit adds function guards to the min and
max aggregate functions in order to provide more feedback.
Depending on how this goes, we can add more guards to the
remaining functions in the future.
-rw-r--r-- | lib/elixir/lib/enum.ex | 19 |
1 files changed, 11 insertions, 8 deletions
diff --git a/lib/elixir/lib/enum.ex b/lib/elixir/lib/enum.ex index 72bef7baa..7f9c87ebb 100644 --- a/lib/elixir/lib/enum.ex +++ b/lib/elixir/lib/enum.ex @@ -1479,7 +1479,8 @@ defmodule Enum do """ @spec max(t, (() -> empty_result)) :: element | empty_result when empty_result: any - def max(enumerable, empty_fallback \\ fn -> raise Enum.EmptyError end) do + def max(enumerable, empty_fallback \\ fn -> raise Enum.EmptyError end) + when is_function(empty_fallback, 0) do aggregate(enumerable, &Kernel.max/2, empty_fallback) end @@ -1507,7 +1508,8 @@ defmodule Enum do """ @spec max_by(t, (element -> any), (() -> empty_result)) :: element | empty_result when empty_result: any - def max_by(enumerable, fun, empty_fallback \\ fn -> raise Enum.EmptyError end) do + def max_by(enumerable, fun, empty_fallback \\ fn -> raise Enum.EmptyError end) + when is_function(fun, 1) and is_function(empty_fallback, 0) do first_fun = &{&1, fun.(&1)} reduce_fun = fn entry, {_, fun_max} = old -> @@ -1599,7 +1601,8 @@ defmodule Enum do """ @spec min(t, (() -> empty_result)) :: element | empty_result when empty_result: any - def min(enumerable, empty_fallback \\ fn -> raise Enum.EmptyError end) do + def min(enumerable, empty_fallback \\ fn -> raise Enum.EmptyError end) + when is_function(empty_fallback, 0) do aggregate(enumerable, &Kernel.min/2, empty_fallback) end @@ -1627,7 +1630,8 @@ defmodule Enum do """ @spec min_by(t, (element -> any), (() -> empty_result)) :: element | empty_result when empty_result: any - def min_by(enumerable, fun, empty_fallback \\ fn -> raise Enum.EmptyError end) do + def min_by(enumerable, fun, empty_fallback \\ fn -> raise Enum.EmptyError end) + when is_function(fun, 1) and is_function(empty_fallback, 0) do first_fun = &{&1, fun.(&1)} reduce_fun = fn entry, {_, fun_min} = old -> @@ -1664,11 +1668,11 @@ defmodule Enum do when empty_result: any def min_max(enumerable, empty_fallback \\ fn -> raise Enum.EmptyError end) - def min_max(first..last, _empty_fallback) do + def min_max(first..last, empty_fallback) when is_function(empty_fallback, 0) do {Kernel.min(first, last), Kernel.max(first, last)} end - def min_max(enumerable, empty_fallback) do + def min_max(enumerable, empty_fallback) when is_function(empty_fallback, 0) do first_fun = &{&1, &1} reduce_fun = fn entry, {min, max} -> @@ -1706,8 +1710,7 @@ defmodule Enum do @spec min_max_by(t, (element -> any), (() -> empty_result)) :: {element, element} | empty_result when empty_result: any def min_max_by(enumerable, fun, empty_fallback \\ fn -> raise Enum.EmptyError end) - - def min_max_by(enumerable, fun, empty_fallback) do + when is_function(fun, 1) and is_function(empty_fallback, 0) do first_fun = fn entry -> fun_entry = fun.(entry) {{entry, entry}, {fun_entry, fun_entry}} |