Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/elixir/lib/code.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1264,8 +1264,8 @@ defmodule Code do
@doc """
Stores all given compilation options.

To store individual options, see `put_compiler_option/2`.
For a description of all options, see `put_compiler_option/2`.
To store individual options and for a description of all
options, see `put_compiler_option/2`.

## Examples

Expand Down
38 changes: 25 additions & 13 deletions lib/elixir/lib/kernel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4488,19 +4488,13 @@ defmodule Kernel do

defmacro defmodule(alias, do: block) do
env = __CALLER__
boot? = bootstrapped?(Macro)

expanded =
case boot? do
true -> Macro.expand(alias, env)
false -> alias
end
expanded = expand_module_alias(alias, env)

{expanded, with_alias} =
case boot? and is_atom(expanded) do
case is_atom(expanded) do
true ->
# Expand the module considering the current environment/nesting
{full, old, new} = expand_module(alias, expanded, env)
{full, old, new} = alias_defmodule(alias, expanded, env)
meta = [defined: full, context: env.module] ++ alias_meta(alias)
{full, {:alias, meta, [old, [as: new, warn: false]]}}

Expand Down Expand Up @@ -4540,16 +4534,34 @@ defmodule Kernel do
defp alias_meta({:__aliases__, meta, _}), do: meta
defp alias_meta(_), do: []

# We don't want to trace :alias_reference since we are defining the alias
defp expand_module_alias({:__aliases__, _, _} = original, env) do
case :elixir_aliases.expand_or_concat(original, env) do
receiver when is_atom(receiver) ->
receiver

aliases ->
aliases = :lists.map(&Macro.expand(&1, env), aliases)

case :lists.all(&is_atom/1, aliases) do
true -> :elixir_aliases.concat(aliases)
false -> original
end
end
end

defp expand_module_alias(other, env), do: Macro.expand(other, env)

# defmodule Elixir.Alias
defp expand_module({:__aliases__, _, [:"Elixir", _ | _]}, module, _env),
defp alias_defmodule({:__aliases__, _, [:"Elixir", _ | _]}, module, _env),
do: {module, module, nil}

# defmodule Alias in root
defp expand_module({:__aliases__, _, _}, module, %{module: nil}),
defp alias_defmodule({:__aliases__, _, _}, module, %{module: nil}),
do: {module, module, nil}

# defmodule Alias nested
defp expand_module({:__aliases__, _, [h | t]}, _module, env) when is_atom(h) do
defp alias_defmodule({:__aliases__, _, [h | t]}, _module, env) when is_atom(h) do
module = :elixir_aliases.concat([env.module, h])
alias = String.to_atom("Elixir." <> Atom.to_string(h))

Expand All @@ -4560,7 +4572,7 @@ defmodule Kernel do
end

# defmodule _
defp expand_module(_raw, module, _env) do
defp alias_defmodule(_raw, module, _env) do
{module, module, nil}
end

Expand Down
2 changes: 1 addition & 1 deletion lib/elixir/lib/macro.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1475,7 +1475,7 @@ defmodule Macro do
end

defp do_expand_once({:__aliases__, meta, _} = original, env) do
case :elixir_aliases.expand(original, env) do
case :elixir_aliases.expand_or_concat(original, env) do
receiver when is_atom(receiver) ->
:elixir_env.trace({:alias_reference, meta, receiver}, env)
{receiver, true}
Expand Down
1 change: 1 addition & 0 deletions lib/elixir/lib/map_set.ex
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ defmodule MapSet do
end

defimpl Collectable do
# TODO: Optimize into an empty mapset by using :maps.from_keys/2 on Erlang/OTP 24+
def into(map_set) do
fun = fn
list, {:cont, x} -> [{x, []} | list]
Expand Down
10 changes: 9 additions & 1 deletion lib/elixir/src/elixir_aliases.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-module(elixir_aliases).
-export([inspect/1, last/1, concat/1, safe_concat/1, format_error/1,
ensure_loaded/3, expand/2, store/5]).
ensure_loaded/3, expand/2, expand_or_concat/2, store/5]).
-include("elixir.hrl").

inspect(Atom) when is_atom(Atom) ->
Expand Down Expand Up @@ -75,6 +75,14 @@ expand({'__aliases__', Meta, [H | T]}, Aliases, E) when is_atom(H) ->
expand({'__aliases__', _Meta, List}, _Aliases, _E) ->
List.

%% Expands or concat if possible.

expand_or_concat(Aliases, E) ->
case expand(Aliases, E) of
[H | T] when is_atom(H) -> concat([H | T]);
AtomOrList -> AtomOrList
end.

%% Ensure a module is loaded before its usage.

ensure_loaded(_Meta, 'Elixir.Kernel', _E) -> ok;
Expand Down
14 changes: 13 additions & 1 deletion lib/elixir/src/elixir_compiler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,24 @@ fast_compile({'__block__', _, Exprs}, E) ->
lists:foldl(fun(Expr, _) -> fast_compile(Expr, E) end, nil, Exprs);
fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
E = NoLineE#{line := ?line(Meta)},

Block = {'__block__', Meta, [
{'=', Meta, [{result, Meta, ?MODULE}, TailBlock]},
{{'.', Meta, [elixir_utils, noop]}, Meta, []},
{result, Meta, ?MODULE}
]},
Expanded = 'Elixir.Macro':expand(Mod, E),

Expanded = case Mod of
{'__aliases__', _, _} ->
case elixir_aliases:expand_or_concat(Mod, E) of
Receiver when is_atom(Receiver) -> Receiver;
_ -> 'Elixir.Macro':expand(Mod, E)
end;

_ ->
'Elixir.Macro':expand(Mod, E)
end,

elixir_module:compile(Expanded, Block, [], E).

%% Bootstrapper
Expand Down
3 changes: 2 additions & 1 deletion lib/elixir/src/elixir_expand.erl
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ expand_without_aliases_report(Other, S, E) ->
expand(Other, S, E).

expand_aliases({'__aliases__', Meta, _} = Alias, S, E, Report) ->
case elixir_aliases:expand(Alias, E) of
case elixir_aliases:expand_or_concat(Alias, E) of
Receiver when is_atom(Receiver) ->
Report andalso elixir_env:trace({alias_reference, Meta, Receiver}, E),
{Receiver, S, E};
Expand All @@ -995,6 +995,7 @@ expand_aliases({'__aliases__', Meta, _} = Alias, S, E, Report) ->
Receiver = elixir_aliases:concat(EAliases),
Report andalso elixir_env:trace({alias_reference, Meta, Receiver}, E),
{Receiver, SA, EA};

false ->
form_error(Meta, E, ?MODULE, {invalid_alias, Alias})
end
Expand Down
1 change: 0 additions & 1 deletion lib/elixir/src/elixir_module.erl
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ compile(Line, Module, Block, Vars, E) ->

try
put_compiler_modules([Module | CompilerModules]),
elixir_env:trace({defmodule, [{line, Line}]}, E),
{Result, NE} = eval_form(Line, Module, DataBag, Block, Vars, E),
CheckerInfo = get(elixir_checker_info),

Expand Down
44 changes: 44 additions & 0 deletions lib/elixir/test/elixir/kernel/lexical_tracker_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,41 @@ defmodule Kernel.LexicalTrackerTest do
refute String in runtime
end

test "structs are exports or compile time" do
{{compile, exports, runtime, _}, _binding} =
Code.eval_string("""
defmodule Kernel.LexicalTrackerTest.StructRuntime do
def expand, do: %URI{}
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
end |> elem(3)
""")

refute URI in compile
assert URI in exports
assert URI in runtime

{{compile, exports, runtime, _}, _binding} =
Code.eval_string("""
defmodule Kernel.LexicalTrackerTest.StructCompile do
_ = %URI{}
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
end |> elem(3)
""")

assert URI in compile
assert URI in exports
refute URI in runtime
end

test "Macro.struct! adds an export dependency" do
{{compile, exports, runtime, _}, _binding} =
Code.eval_string("""
defmodule Kernel.LexicalTrackerTest.MacroStruct do
# We do not use the alias because it would be a compile time
# dependency. The alias may happen in practice, which is the
# mechanism to make this expansion become a compile-time one.
# However, in some cases, such as typespecs, we don't necessarily
# want the compile-time dependency to happen.
Macro.struct!(:"Elixir.URI", __ENV__)
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
end |> elem(3)
Expand All @@ -237,5 +268,18 @@ defmodule Kernel.LexicalTrackerTest do
refute URI in exports
assert URI in runtime
end

test "defmodule does not add a compile dependency" do
{{compile, exports, runtime, _}, _binding} =
Code.eval_string("""
defmodule Kernel.LexicalTrackerTest.Defmodule do
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
end |> elem(3)
""")

refute Kernel.LexicalTrackerTest.Defmodule in compile
refute Kernel.LexicalTrackerTest.Defmodule in exports
refute Kernel.LexicalTrackerTest.Defmodule in runtime
end
end
end
2 changes: 0 additions & 2 deletions lib/elixir/test/elixir/kernel/tracers_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@ defmodule Kernel.TracersTest do
end
""")

assert_receive {{:defmodule, _meta}, %{module: Sample}}

assert_receive {{:local_macro, meta, :foo, 1}, _}
assert meta[:line] == 4
assert meta[:column] == 21
Expand Down
Loading