summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@plataformatec.com.br>2018-04-18 17:22:55 +0200
committerJosé Valim <jose.valim@plataformatec.com.br>2018-04-18 17:22:55 +0200
commitc6bfb7b0c0908b271fad6a83c8d9c4f1481f3776 (patch)
tree6d6f48a2880031e9d244b3ec0845daa5a41094db
parentadbd8da0f613dd807c6d637f81ec507b008cc745 (diff)
downloadelixir-c6bfb7b0c0908b271fad6a83c8d9c4f1481f3776.tar.gz
Consider hygienic vars in defguard, closes #7566
-rw-r--r--lib/elixir/lib/kernel/utils.ex28
-rw-r--r--lib/elixir/test/elixir/kernel/guard_test.exs21
2 files changed, 41 insertions, 8 deletions
diff --git a/lib/elixir/lib/kernel/utils.ex b/lib/elixir/lib/kernel/utils.ex
index 52242196d..9138c60f6 100644
--- a/lib/elixir/lib/kernel/utils.ex
+++ b/lib/elixir/lib/kernel/utils.ex
@@ -201,8 +201,8 @@ defmodule Kernel.Utils do
defp extract_refs_from_args(args) do
Macro.postwalk(args, [], fn
- {ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
- {var, [{ref, context} | acc]}
+ {ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
+ {var, [{ref, var_context(meta, context)} | acc]}
node, acc ->
{node, acc}
@@ -212,8 +212,8 @@ defmodule Kernel.Utils do
# Finds every reference to `refs` in `guard` and wraps them in an unquote.
defp unquote_every_ref(guard, refs) do
Macro.postwalk(guard, fn
- {ref, _meta, context} = var when is_atom(ref) and is_atom(context) ->
- case {ref, context} in refs do
+ {ref, meta, context} = var when is_atom(ref) and is_atom(context) ->
+ case {ref, var_context(meta, context)} in refs do
true -> literal_unquote(var)
false -> var
end
@@ -227,9 +227,11 @@ defmodule Kernel.Utils do
defp unquote_refs_once(guard, refs) do
{_, used_refs} =
Macro.postwalk(guard, [], fn
- {ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
- case {ref, context} in refs and {ref, context} not in acc do
- true -> {var, [{ref, context} | acc]}
+ {ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
+ pair = {ref, var_context(meta, context)}
+
+ case pair in refs and pair not in acc do
+ true -> {var, [pair | acc]}
false -> {var, acc}
end
@@ -237,7 +239,7 @@ defmodule Kernel.Utils do
{node, acc}
end)
- vars = for {ref, context} <- :lists.reverse(used_refs), do: {ref, [], context}
+ vars = for {ref, context} <- :lists.reverse(used_refs), do: context_to_var(ref, context)
exprs = for var <- vars, do: literal_unquote(var)
quote do
@@ -253,4 +255,14 @@ defmodule Kernel.Utils do
defp literal_unquote(ast) do
{:unquote, [], List.wrap(ast)}
end
+
+ defp context_to_var(ref, ctx) when is_atom(ctx), do: {ref, [], ctx}
+ defp context_to_var(ref, ctx) when is_integer(ctx), do: {ref, [counter: ctx], nil}
+
+ defp var_context(meta, kind) do
+ case :lists.keyfind(:counter, 1, meta) do
+ {:counter, counter} -> counter
+ false -> kind
+ end
+ end
end
diff --git a/lib/elixir/test/elixir/kernel/guard_test.exs b/lib/elixir/test/elixir/kernel/guard_test.exs
index cbf1d0899..e18e1e326 100644
--- a/lib/elixir/test/elixir/kernel/guard_test.exs
+++ b/lib/elixir/test/elixir/kernel/guard_test.exs
@@ -109,6 +109,27 @@ defmodule Kernel.GuardTest do
end
end
+ defmodule GuardFromMacro do
+ defmacro __using__(_) do
+ quote do
+ defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
+ end
+ end
+ end
+
+ test "defguard defines a guard from inside another macro" do
+ defmodule UseGuardFromMacro do
+ use GuardFromMacro
+
+ def assert! do
+ assert is_even(0)
+ refute is_even(1)
+ end
+ end
+
+ UseGuardFromMacro.assert!()
+ end
+
defmodule IntegerPrivateGuards do
defguardp is_even(value) when is_integer(value) and rem(value, 2) == 0