Skip to content

Commit 705a637

Browse files
author
José Valim
committed
Consider hygienic vars in defguard, closes #7566
Signed-off-by: José Valim <[email protected]>
1 parent 6b74116 commit 705a637

File tree

2 files changed

+41
-8
lines changed

2 files changed

+41
-8
lines changed

lib/elixir/lib/kernel/utils.ex

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ defmodule Kernel.Utils do
200200

201201
defp extract_refs_from_args(args) do
202202
Macro.postwalk(args, [], fn
203-
{ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
204-
{var, [{ref, context} | acc]}
203+
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
204+
{var, [{ref, var_context(meta, context)} | acc]}
205205

206206
node, acc ->
207207
{node, acc}
@@ -211,8 +211,8 @@ defmodule Kernel.Utils do
211211
# Finds every reference to `refs` in `guard` and wraps them in an unquote.
212212
defp unquote_every_ref(guard, refs) do
213213
Macro.postwalk(guard, fn
214-
{ref, _meta, context} = var when is_atom(ref) and is_atom(context) ->
215-
case {ref, context} in refs do
214+
{ref, meta, context} = var when is_atom(ref) and is_atom(context) ->
215+
case {ref, var_context(meta, context)} in refs do
216216
true -> literal_unquote(var)
217217
false -> var
218218
end
@@ -226,17 +226,19 @@ defmodule Kernel.Utils do
226226
defp unquote_refs_once(guard, refs) do
227227
{_, used_refs} =
228228
Macro.postwalk(guard, [], fn
229-
{ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
230-
case {ref, context} in refs and {ref, context} not in acc do
231-
true -> {var, [{ref, context} | acc]}
229+
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
230+
pair = {ref, var_context(meta, context)}
231+
232+
case pair in refs and pair not in acc do
233+
true -> {var, [pair | acc]}
232234
false -> {var, acc}
233235
end
234236

235237
node, acc ->
236238
{node, acc}
237239
end)
238240

239-
vars = for {ref, context} <- :lists.reverse(used_refs), do: {ref, [], context}
241+
vars = for {ref, context} <- :lists.reverse(used_refs), do: context_to_var(ref, context)
240242
exprs = for var <- vars, do: literal_unquote(var)
241243

242244
quote do
@@ -252,4 +254,14 @@ defmodule Kernel.Utils do
252254
defp literal_unquote(ast) do
253255
{:unquote, [], List.wrap(ast)}
254256
end
257+
258+
defp context_to_var(ref, ctx) when is_atom(ctx), do: {ref, [], ctx}
259+
defp context_to_var(ref, ctx) when is_integer(ctx), do: {ref, [counter: ctx], nil}
260+
261+
defp var_context(meta, kind) do
262+
case :lists.keyfind(:counter, 1, meta) do
263+
{:counter, counter} -> counter
264+
false -> kind
265+
end
266+
end
255267
end

lib/elixir/test/elixir/kernel/guard_test.exs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,27 @@ defmodule Kernel.GuardTest do
109109
end
110110
end
111111

112+
defmodule GuardFromMacro do
113+
defmacro __using__(_) do
114+
quote do
115+
defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
116+
end
117+
end
118+
end
119+
120+
test "defguard defines a guard from inside another macro" do
121+
defmodule UseGuardFromMacro do
122+
use GuardFromMacro
123+
124+
def assert! do
125+
assert is_even(0)
126+
refute is_even(1)
127+
end
128+
end
129+
130+
UseGuardFromMacro.assert!()
131+
end
132+
112133
defmodule IntegerPrivateGuards do
113134
defguardp is_even(value) when is_integer(value) and rem(value, 2) == 0
114135

0 commit comments

Comments
 (0)