summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@gmail.com>2018-12-08 10:12:59 +0100
committerGitHub <noreply@github.com>2018-12-08 10:12:59 +0100
commit0cc21e36b26f4d9446b786465bbcfc2d1b998051 (patch)
treefd0d7a032b44ecaaeccf155501fcbd1d34b94569
parent5f3e1217bfe5263b3bd4c6ccca61f986e9d038e0 (diff)
downloadelixir-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.ex19
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}}