Skip to content

Commit 3c4c68b

Browse files
committed
Fix variable overriding in records, closes #10540
1 parent bed0a68 commit 3c4c68b

File tree

3 files changed

+74
-10
lines changed

3 files changed

+74
-10
lines changed

lib/elixir/lib/macro.ex

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,13 @@ defmodule Macro do
337337
end
338338

339339
@doc """
340-
Generates AST nodes for a given number of required argument variables using
341-
`Macro.var/2`.
340+
Generates AST nodes for a given number of required argument
341+
variables using `Macro.var/2`.
342+
343+
Note the arguments are not unique. If you later on want
344+
to access this same varibles, you can invoke this function
345+
with the same inputs. Use `generate_unique_arguments/2` to
346+
generate a unique arguments that can't be overridden.
342347
343348
## Examples
344349
@@ -349,19 +354,47 @@ defmodule Macro do
349354
@doc since: "1.5.0"
350355
@spec generate_arguments(0, context :: atom) :: []
351356
@spec generate_arguments(pos_integer, context) :: [{atom, [], context}, ...] when context: atom
352-
def generate_arguments(amount, context)
357+
def generate_arguments(amount, context), do: generate_arguments(amount, context, &var/2)
358+
359+
@doc """
360+
Generates AST nodes for a given number of required argument
361+
variables using `Macro.unique_var/2`.
362+
363+
## Examples
353364
354-
def generate_arguments(0, context) when is_atom(context), do: []
365+
iex> [var1, var2] = Macro.generate_unique_arguments(2, __MODULE__)
366+
iex> {:arg1, [counter: c1], __MODULE__} = var1
367+
iex> {:arg2, [counter: c2], __MODULE__} = var2
368+
iex> is_integer(c1) and is_integer(c2)
369+
true
370+
371+
"""
372+
@doc since: "1.11.3"
373+
@spec generate_unique_arguments(0, context :: atom) :: []
374+
@spec generate_unique_arguments(pos_integer, context) :: [
375+
{atom, [counter: integer], context},
376+
...
377+
]
378+
when context: atom
379+
def generate_unique_arguments(amount, context),
380+
do: generate_arguments(amount, context, &unique_var/2)
355381

356-
def generate_arguments(amount, context)
357-
when is_integer(amount) and amount > 0 and is_atom(context) do
358-
for id <- 1..amount, do: var(String.to_atom("arg" <> Integer.to_string(id)), context)
382+
defp generate_arguments(0, context, _fun) when is_atom(context), do: []
383+
384+
defp generate_arguments(amount, context, fun)
385+
when is_integer(amount) and amount > 0 and is_atom(context) do
386+
for id <- 1..amount, do: fun.(String.to_atom("arg" <> Integer.to_string(id)), context)
359387
end
360388

361389
@doc """
362390
Generates an AST node representing the variable given
363391
by the atoms `var` and `context`.
364392
393+
Note this variable is not unique. If you later on want
394+
to access this same varible, you can invoke `var/2`
395+
again with the same argument. Use `unique_var/2` to
396+
generate a unique variable that can't be overridden.
397+
365398
## Examples
366399
367400
In order to build a variable, a context is expected.
@@ -383,6 +416,24 @@ defmodule Macro do
383416
{var, [], context}
384417
end
385418

419+
@doc """
420+
Generates an AST node representing a unique variable
421+
given by the atoms `var` and `context`.
422+
423+
## Examples
424+
425+
iex> {:foo, [counter: c], __MODULE__} = Macro.unique_var(:foo, __MODULE__)
426+
iex> is_integer(c)
427+
true
428+
429+
"""
430+
@doc since: "1.11.3"
431+
@spec unique_var(var, context) :: {var, [counter: integer], context}
432+
when var: atom, context: atom
433+
def unique_var(var, context) when is_atom(var) and is_atom(context) do
434+
{var, [counter: :elixir_module.next_counter(context)], context}
435+
end
436+
386437
@doc """
387438
Performs a depth-first traversal of quoted expressions
388439
using an accumulator.

lib/elixir/lib/record.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,12 +447,12 @@ defmodule Record do
447447
end
448448
end
449449

450-
defp hoist_expressions(keyword, %{context: nil}) do
450+
defp hoist_expressions(keyword, %{context: nil, module: module}) do
451451
Enum.map_reduce(keyword, [], fn {key, expr}, acc ->
452452
if simple_argument?(expr) do
453453
{{key, expr}, acc}
454454
else
455-
var = Macro.var(key, __MODULE__)
455+
var = Macro.unique_var(key, module)
456456
{{key, var}, [{:=, [], [var, expr]} | acc]}
457457
end
458458
end)

lib/elixir/test/elixir/record_test.exs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,20 @@ defmodule RecordTest do
156156
)
157157

158158
assert user == {RecordTest, :name, {RecordTest, :inner_name, :inner_age}}
159-
assert Process.info(self(), :messages) == {:messages, [:inner_age, :inner_name, :name]}
159+
assert for(_ <- 1..3, do: assert_receive(_)) == [:inner_age, :inner_name, :name]
160+
161+
user =
162+
user(
163+
name: send(self(), :name),
164+
age:
165+
user(
166+
age: send(self(), :inner_age),
167+
name: send(self(), :inner_name)
168+
)
169+
)
170+
171+
assert user == {RecordTest, :name, {RecordTest, :inner_name, :inner_age}}
172+
assert for(_ <- 1..3, do: assert_receive(_)) == [:name, :inner_age, :inner_name]
160173
end
161174

162175
Record.defrecord(

0 commit comments

Comments
 (0)