Skip to content

Commit 3d2c310

Browse files
committed
Add Macro.expand_literals/2 and do partial literal expansion in @
Closes #12174.
1 parent 278eba5 commit 3d2c310

File tree

4 files changed

+113
-45
lines changed

4 files changed

+113
-45
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3590,33 +3590,31 @@ defmodule Kernel do
35903590
defp collect_traces(:after_verify, arg, _env), do: {arg, []}
35913591
defp collect_traces(:on_definition, arg, _env), do: {arg, []}
35923592

3593-
defp collect_traces(_name, arg, env) do
3594-
case is_pid(env.lexical_tracker) and Macro.quoted_literal?(arg) do
3595-
true ->
3596-
env = %{env | function: {:__info__, 1}}
3597-
3598-
{arg, aliases} =
3599-
Macro.prewalk(arg, %{}, fn
3600-
{:__aliases__, _, _} = alias, acc ->
3601-
case Macro.expand(alias, env) do
3602-
atom when is_atom(atom) -> {atom, Map.put(acc, atom, [])}
3603-
_ -> {alias, acc}
3604-
end
3605-
3606-
node, acc ->
3607-
{node, acc}
3608-
end)
3593+
defp collect_traces(_name, arg, %{lexical_tracker: pid} = env) when is_pid(pid) do
3594+
env = %{env | function: {:__info__, 1}}
3595+
3596+
{arg, aliases} =
3597+
Macro.expand_literals(arg, %{}, fn
3598+
{:__aliases__, _, _} = alias, acc ->
3599+
case Macro.expand(alias, env) do
3600+
atom when is_atom(atom) -> {atom, Map.put(acc, atom, [])}
3601+
_ -> {alias, acc}
3602+
end
36093603

3610-
case map_size(aliases) do
3611-
0 -> {arg, []}
3612-
_ -> {arg, [{env.line, env.lexical_tracker, env.tracers, :maps.keys(aliases)}]}
3613-
end
3604+
node, acc ->
3605+
{Macro.expand(node, env), acc}
3606+
end)
36143607

3615-
false ->
3616-
{arg, []}
3608+
case map_size(aliases) do
3609+
0 -> {arg, []}
3610+
_ -> {arg, [{env.line, env.lexical_tracker, env.tracers, :maps.keys(aliases)}]}
36173611
end
36183612
end
36193613

3614+
defp collect_traces(_name, arg, _env) do
3615+
{arg, []}
3616+
end
3617+
36203618
defp typespec?(:type), do: true
36213619
defp typespec?(:typep), do: true
36223620
defp typespec?(:opaque), do: true

lib/elixir/lib/macro.ex

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,42 +1966,95 @@ defmodule Macro do
19661966

19671967
def quoted_literal?({:%{}, _, args}), do: quoted_literal?(args)
19681968
def quoted_literal?({:{}, _, args}), do: quoted_literal?(args)
1969+
def quoted_literal?({:__MODULE__, _, ctx}) when is_atom(ctx), do: true
19691970
def quoted_literal?({left, right}), do: quoted_literal?(left) and quoted_literal?(right)
19701971
def quoted_literal?(list) when is_list(list), do: :lists.all(&quoted_literal?/1, list)
19711972
def quoted_literal?(term), do: is_atom(term) or is_number(term) or is_binary(term)
19721973

1973-
@doc """
1974-
Expands the `ast` representing a quoted literal within the given `env`.
1974+
@doc false
1975+
@deprecated "Use Macro.expand_literals/2 instead"
1976+
def expand_literal(ast, env) do
1977+
expand_literals(ast, env)
1978+
end
19751979

1976-
This function checks if the given AST represents a quoted literal
1977-
(using `quoted_literal?/1`) and then expands all relevant nodes.
1978-
If a literal node is not given, then it returns the AST as is.
1979-
At the moment, the only expandable literal nodes in an AST are
1980-
aliases, so this function only expands aliases.
1980+
@doc """
1981+
Expands all literals in `ast` with the given `env`.
19811982
19821983
This function is mostly used to remove compile-time dependencies
19831984
from AST nodes. In such cases, the given environment is usually
1984-
manipulate to represent a function:
1985+
manipulated to represent a function:
19851986
1986-
Macro.expand_literal(ast, %{env | function: {:my_code, 1}})
1987+
Macro.expand_literals(ast, %{env | function: {:my_code, 1}})
1988+
1989+
At the moment, the only expandable literal nodes in an AST are
1990+
aliases, so this function only expands aliases.
19871991
19881992
However, be careful when removing compile-time dependencies between
19891993
modules. If you remove them but you still invoke the module at
1990-
compile-time, you may end-up with broken behaviour.
1994+
compile-time, Elixir will be unable to properly recompile modules
1995+
when they change.
19911996
"""
1992-
@doc since: "1.14.0"
1993-
@spec expand_literal(t(), Macro.Env.t()) :: t()
1994-
def expand_literal(ast, env) do
1995-
if quoted_literal?(ast) do
1996-
prewalk(ast, fn
1997-
{:__aliases__, _, _} = alias -> expand(alias, env)
1998-
other -> other
1999-
end)
1997+
@doc since: "1.14.1"
1998+
@spec expand_literals(t(), Macro.Env.t()) :: t()
1999+
def expand_literals(ast, env) do
2000+
{ast, :ok} = expand_literals(ast, :ok, fn node, :ok -> {expand(node, env), :ok} end)
2001+
ast
2002+
end
2003+
2004+
@doc """
2005+
Expands all literals in `ast` with the given `acc` and `fun`.
2006+
2007+
`fun` will be invoked with an expandable AST node and `acc` and
2008+
must return a new node with `acc`. This is a general version of
2009+
`expand_literals/2` which supports a custom expansion function.
2010+
Please check `expand_literals/2` for use cases and pitfalls.
2011+
"""
2012+
@doc since: "1.14.1"
2013+
@spec expand_literals(t(), acc, (t(), acc -> {t(), acc})) :: t() when acc: term()
2014+
def expand_literals(ast, acc, fun)
2015+
2016+
def expand_literals({:__aliases__, meta, args}, acc, fun) do
2017+
{args, acc} = expand_literals(args, acc, fun)
2018+
2019+
if :lists.all(&is_atom/1, args) do
2020+
fun.({:__aliases__, meta, args}, acc)
20002021
else
2001-
ast
2022+
{{:__aliases__, meta, args}, acc}
20022023
end
20032024
end
20042025

2026+
def expand_literals({:__MODULE__, _meta, ctx} = node, acc, fun) when is_atom(ctx) do
2027+
fun.(node, acc)
2028+
end
2029+
2030+
def expand_literals({:%, meta, [left, right]}, acc, fun) do
2031+
{left, acc} = expand_literals(left, acc, fun)
2032+
{right, acc} = expand_literals(right, acc, fun)
2033+
{{:%, meta, [left, right]}, acc}
2034+
end
2035+
2036+
def expand_literals({:%{}, meta, args}, acc, fun) do
2037+
{args, acc} = expand_literals(args, acc, fun)
2038+
{{:%{}, meta, args}, acc}
2039+
end
2040+
2041+
def expand_literals({:{}, meta, args}, acc, fun) do
2042+
{args, acc} = expand_literals(args, acc, fun)
2043+
{{:{}, meta, args}, acc}
2044+
end
2045+
2046+
def expand_literals({left, right}, acc, fun) do
2047+
{left, acc} = expand_literals(left, acc, fun)
2048+
{right, acc} = expand_literals(right, acc, fun)
2049+
{{left, right}, acc}
2050+
end
2051+
2052+
def expand_literals(list, acc, fun) when is_list(list) do
2053+
:lists.mapfoldl(&expand_literals(&1, &2, fun), acc, list)
2054+
end
2055+
2056+
def expand_literals(term, acc, _fun), do: {term, acc}
2057+
20052058
@doc """
20062059
Receives an AST node and expands it until it can no longer
20072060
be expanded.

lib/elixir/test/elixir/kernel/lexical_tracker_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ defmodule Kernel.LexicalTrackerTest do
162162
{{compile, _, _, _}, _binding} =
163163
Code.eval_string("""
164164
defmodule Kernel.LexicalTrackerTest.Attribute1 do
165-
@example [String, Enum]
165+
@example [String, Enum, 3 + 10]
166166
def foo(atom) when atom in @example, do: atom
167167
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
168168
end |> elem(3)

lib/elixir/test/elixir/macro_test.exs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,26 @@ defmodule MacroTest do
269269
assert expand_and_clean(quote(do: oror(1, false)), __ENV__) == quoted
270270
end
271271

272-
test "expand_literal" do
273-
assert Macro.expand_literal(quote(do: Foo), __ENV__) == Foo
274-
assert Macro.expand_literal(quote(do: Foo + Bar), __ENV__) == quote(do: Foo + Bar)
272+
test "expand_literals/2" do
273+
assert Macro.expand_literals(quote(do: Foo), __ENV__) == Foo
274+
assert Macro.expand_literals(quote(do: Foo + Bar), __ENV__) == quote(do: Foo + Bar)
275+
assert Macro.expand_literals(quote(do: __MODULE__), __ENV__) == __MODULE__
276+
assert Macro.expand_literals(quote(do: __MODULE__.Foo), __ENV__) == __MODULE__.Foo
277+
assert Macro.expand_literals(quote(do: [Foo, 1 + 2]), __ENV__) == [Foo, quote(do: 1 + 2)]
278+
end
279+
280+
test "expand_literals/3" do
281+
fun = fn node, acc ->
282+
expanded = Macro.expand(node, __ENV__)
283+
{expanded, [expanded | acc]}
284+
end
285+
286+
assert Macro.expand_literals(quote(do: Foo), [], fun) == {Foo, [Foo]}
287+
assert Macro.expand_literals(quote(do: Foo + Bar), [], fun) == {quote(do: Foo + Bar), []}
288+
assert Macro.expand_literals(quote(do: __MODULE__), [], fun) == {__MODULE__, [__MODULE__]}
289+
290+
assert Macro.expand_literals(quote(do: __MODULE__.Foo), [], fun) ==
291+
{__MODULE__.Foo, [__MODULE__.Foo, __MODULE__]}
275292
end
276293

277294
test "var/2" do

0 commit comments

Comments
 (0)