Skip to content

Commit 53c93b9

Browse files
committed
Avoid nesting of capture inside macros
Unfortunately this makes it so the unused capture warnings emit false positives, so this particular warning was removed. Closes #13609.
1 parent a7bf120 commit 53c93b9

File tree

7 files changed

+30
-31
lines changed

7 files changed

+30
-31
lines changed

lib/elixir/lib/macro.ex

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,10 +495,8 @@ defmodule Macro do
495495
"""
496496
@doc since: "1.11.3"
497497
@spec generate_unique_arguments(0, context :: atom) :: []
498-
@spec generate_unique_arguments(pos_integer, context) :: [
499-
{atom, [counter: integer], context},
500-
...
501-
]
498+
@spec generate_unique_arguments(pos_integer, context) ::
499+
[{atom, [counter: integer], context}, ...]
502500
when context: atom
503501
def generate_unique_arguments(amount, context),
504502
do: generate_arguments(amount, context, &unique_var/2)

lib/elixir/src/elixir_env.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ reset_unused_vars(#elixir_ex{unused={_Unused, Version}} = S) ->
9191

9292
check_unused_vars(#elixir_ex{unused={Unused, _Version}}, E) ->
9393
[elixir_errors:file_warn(calculate_span(Meta, Name), E, ?MODULE, {unused_var, Name, Overridden}) ||
94-
{{{Name, nil}, _}, {Meta, Overridden}} <- maps:to_list(Unused), is_unused_var(Name)],
94+
{{{Name, _Kind}, _Count}, {Meta, Overridden}} <- maps:to_list(Unused), is_unused_var(Name)],
9595
E.
9696

9797
calculate_span(Meta, Name) ->
@@ -120,8 +120,8 @@ merge_and_check_unused_vars(Current, Unused, ClauseUnused, E) ->
120120
Acc
121121
end;
122122

123-
({{Name, Kind}, _Count}, {Meta, Overridden}, Acc) ->
124-
case (Kind == nil) andalso is_unused_var(Name) of
123+
({{Name, _Kind}, _Count}, {Meta, Overridden}, Acc) ->
124+
case is_unused_var(Name) of
125125
true ->
126126
Warn = {unused_var, Name, Overridden},
127127
elixir_errors:file_warn(Meta, E, ?MODULE, Warn);

lib/elixir/src/elixir_expand.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ expand({Name, Meta, Kind}, S, #{context := match} = E) when is_atom(Name), is_at
340340
%% Variable was already overridden
341341
#{Pair := VarVersion} when VarVersion >= PrematchVersion ->
342342
maybe_warn_underscored_var_repeat(Meta, Name, Kind, E),
343-
NewUnused = var_used(Meta, Pair, VarVersion, Unused),
343+
NewUnused = var_used(Pair, Meta, VarVersion, Unused),
344344
Var = {Name, [{version, VarVersion} | Meta], Kind},
345345
{Var, S#elixir_ex{unused={NewUnused, Version}}, E};
346346

@@ -396,7 +396,7 @@ expand({Name, Meta, Kind}, S, E) when is_atom(Name), is_atom(Kind) ->
396396
{ok, PairVersion} ->
397397
maybe_warn_underscored_var_access(Meta, Name, Kind, E),
398398
Var = {Name, [{version, PairVersion} | Meta], Kind},
399-
{Var, S#elixir_ex{unused={var_used(Meta, Pair, PairVersion, Unused), Version}}, E};
399+
{Var, S#elixir_ex{unused={var_used(Pair, Meta, PairVersion, Unused), Version}}, E};
400400

401401
Error ->
402402
case lists:keyfind(if_undefined, 1, Meta) of
@@ -656,7 +656,7 @@ var_unused({_, Kind} = Pair, Meta, Version, Unused, Override) ->
656656
false -> Unused
657657
end.
658658

659-
var_used(Meta, {_, Kind} = Pair, Version, Unused) ->
659+
var_used({_, Kind} = Pair, Meta, Version, Unused) ->
660660
KeepUnused = lists:keymember(keep_unused, 1, Meta),
661661

662662
if

lib/elixir/src/elixir_fn.erl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,18 @@ validate(Meta, [{Pos, _} | _], Expected, E) ->
128128
validate(_Meta, [], _Pos, _E) ->
129129
[].
130130

131-
escape({'&', Meta, [Pos]}, _E, Dict) when is_integer(Pos), Pos > 0 ->
131+
escape({'&', Meta, [Pos]}, E, Dict) when is_integer(Pos), Pos > 0 ->
132132
% Using a nil context here to emit warnings when variable is unused.
133133
% This might pollute user space but is unlikely because variables
134134
% named :"&1" are not valid syntax.
135-
Var = {list_to_atom([$& | integer_to_list(Pos)]), Meta, nil},
136-
{Var, orddict:store(Pos, Var, Dict)};
135+
case orddict:find(Pos, Dict) of
136+
{ok, Var} ->
137+
{Var, Dict};
138+
error ->
139+
Next = elixir_module:next_counter(?key(E, module)),
140+
Var = {capture, [{counter, Next} | Meta], nil},
141+
{Var, orddict:store(Pos, Var, Dict)}
142+
end;
137143
escape({'&', Meta, [Pos]}, E, _Dict) when is_integer(Pos) ->
138144
file_error(Meta, E, ?MODULE, {invalid_arity_for_capture, Pos});
139145
escape({'&', Meta, _} = Arg, E, _Dict) ->

lib/elixir/test/elixir/kernel/expansion_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,9 +1134,9 @@ defmodule Kernel.ExpansionTest do
11341134
end
11351135

11361136
test "keeps position meta on & variables" do
1137-
assert expand(Code.string_to_quoted!("& &1")) ==
1137+
assert expand(Code.string_to_quoted!("& &1")) |> clean_meta([:counter]) ==
11381138
{:fn, [{:line, 1}],
1139-
[{:->, [{:line, 1}], [[{:"&1", [line: 1], nil}], {:"&1", [line: 1], nil}]}]}
1139+
[{:->, [{:line, 1}], [[{:capture, [line: 1], nil}], {:capture, [line: 1], nil}]}]}
11401140
end
11411141

11421142
test "expands remotes" do

lib/elixir/test/elixir/kernel/fn_test.exs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,6 @@ defmodule Kernel.FnTest do
8888
assert is_function(&and/2)
8989
end
9090

91-
test "capture precedence in cons" do
92-
assert [(&IO.puts/1) | &IO.puts/2] == [(&IO.puts/1) | &IO.puts/2]
93-
end
94-
9591
test "capture with variable module" do
9692
mod = List
9793
assert (&mod.flatten(&1)).([1, [2], 3]) == [1, 2, 3]
@@ -140,13 +136,23 @@ defmodule Kernel.FnTest do
140136
assert (&(!is_atom(&1))).(:foo) == false
141137
end
142138

143-
test "capture other" do
139+
test "capture with function call" do
144140
assert (& &1).(:ok) == :ok
145141

146142
fun = fn a, b -> a + b end
147143
assert (&fun.(&1, 2)).(1) == 3
148144
end
149145

146+
defmacro c(x) do
147+
quote do
148+
&(unquote(x) <> &1)
149+
end
150+
end
151+
152+
test "capture within capture through macro" do
153+
assert (&c(&1).("b")).("a") == "ab"
154+
end
155+
150156
defp atom?(atom) when is_atom(atom), do: true
151157
defp atom?(_), do: false
152158

lib/elixir/test/elixir/kernel/warning_test.exs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -463,17 +463,6 @@ defmodule Kernel.WarningTest do
463463
fn x -> match?(x, :value) end
464464
"""
465465
)
466-
467-
assert_warn_eval(
468-
[
469-
"nofile:1",
470-
"variable \"&1\" is unused (this might happen when using a capture argument as a pattern)",
471-
"variable \"&1\" is unused (this might happen when using a capture argument as a pattern)"
472-
],
473-
"""
474-
&match?(&1, :value)
475-
"""
476-
)
477466
end
478467

479468
test "useless literal" do

0 commit comments

Comments
 (0)