Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Required private modules don't survive quote blocks #1

Closed
the-mikedavis opened this issue Mar 5, 2019 · 5 comments
Closed

Required private modules don't survive quote blocks #1

the-mikedavis opened this issue Mar 5, 2019 · 5 comments

Comments

@the-mikedavis
Copy link

@the-mikedavis the-mikedavis commented Mar 5, 2019

Minimum example here.

If we define a private module:

# `lib/using_private/impl.ex`
import Defmodulep

defmodulep UsingPrivate.Impl, visible_to: [UsingPrivate] do
  # implementation functions for the `__using__/1` macro in
  # `lib/using_private.ex`

  def hello do
    IO.puts("hello")
  end
end

And define a quoted block that uses that private module:

# `lib/using_private.ex`
defmodule UsingPrivate do
  defmacro __using__(_opts) do
    quote do
      import Defmodulep
      requirep UsingPrivate.Impl, as: Impl

      def hello, do: Impl.hello()
    end
  end
end

And finally use that quoted block in a fresh module

# `lib/using_private/bad.ex`
defmodule UsingPrivate.Bad do
  use UsingPrivate
end

We get a compiler warning:

$ mix compile --force
==> defmodulep
Compiling 1 file (.ex)
Generated defmodulep app
==> using_private
Compiling 3 files (.ex)
warning: function Impl.hello/0 is undefined (module Impl is not available)
  lib/using_private/bad.ex:2

Generated using_private app

And an error in iex

iex(1)> UsingPrivate.Fixture.hello
** (UndefinedFunctionError) function Impl.hello/0 is undefined (module Impl is not available)
    Impl.hello()

The module is not defined!

If we write the same code without the quoted block, though, everything works as expected:

# `lib/using_private/good.ex`
defmodule UsingPrivate.Good do
  import Defmodulep
  requirep UsingPrivate.Impl, as: Impl

  def hello, do: Impl.hello()
end
@the-mikedavis

This comment has been minimized.

Copy link
Author

@the-mikedavis the-mikedavis commented Mar 5, 2019

The expansions:

-file("/vagrant/code/using_private/lib/using_private"
      "/good.ex",
      1).

-module('Elixir.UsingPrivate.Good').

-compile([no_auto_import]).

-export(['__info__'/1, hello/0]).

-spec '__info__'(attributes | compile | functions |
                 macros | md5 | module | deprecated) -> any().

'__info__'(module) -> 'Elixir.UsingPrivate.Good';
'__info__'(functions) -> [{hello, 0}];
'__info__'(macros) -> [];
'__info__'(Key = attributes) ->
    erlang:get_module_info('Elixir.UsingPrivate.Good', Key);
'__info__'(Key = compile) ->
    erlang:get_module_info('Elixir.UsingPrivate.Good', Key);
'__info__'(Key = md5) ->
    erlang:get_module_info('Elixir.UsingPrivate.Good', Key);
'__info__'(deprecated) -> [].

hello() -> 'Elixirp.UsingPrivate.Impl':hello().
-file("/vagrant/code/using_private/lib/using_private"
      "/bad.ex",
      1).

-module('Elixir.UsingPrivate.Bad').

-compile([no_auto_import]).

-export(['__info__'/1, hello/0]).

-spec '__info__'(attributes | compile | functions |
                 macros | md5 | module | deprecated) -> any().

'__info__'(module) -> 'Elixir.UsingPrivate.Bad';
'__info__'(functions) -> [{hello, 0}];
'__info__'(macros) -> [];
'__info__'(Key = attributes) ->
    erlang:get_module_info('Elixir.UsingPrivate.Bad', Key);
'__info__'(Key = compile) ->
    erlang:get_module_info('Elixir.UsingPrivate.Bad', Key);
'__info__'(Key = md5) ->
    erlang:get_module_info('Elixir.UsingPrivate.Bad', Key);
'__info__'(deprecated) -> [].

hello() -> 'Elixir.Impl':hello().

Looks like the aliasing of Impl is changed by the quoted block in bad.ex

@the-mikedavis

This comment has been minimized.

Copy link
Author

@the-mikedavis the-mikedavis commented Mar 5, 2019

Workaround:

defmodule UsingPrivate do
  defmacro __using__(_opts) do
    quote do
      require :"Elixirp.UsingPrivate.Impl", as: Impl
      def hello, do: Impl.hello()
    end
  end
end

But as you specifically say,

Note that bypassing such behaviour is extremely discouraged otherwise

@josevalim

This comment has been minimized.

Copy link
Owner

@josevalim josevalim commented Mar 5, 2019

@the-mikedavis

This comment has been minimized.

Copy link
Author

@the-mikedavis the-mikedavis commented Mar 5, 2019

Aha @josevalim! I swore I tried that 😅

# `lib/using_private.ex`
defmodule UsingPrivate do
  defmacro __using__(_opts) do
    quote do
      def hello, do: Impl.hello()
    end
  end
end
# `lib/using_private/bad.ex`
defmodule UsingPrivate.Bad do
  import Defmodulep
  requirep UsingPrivate.Impl, as: Impl
  use UsingPrivate
end
$ mix compile --force
Compiling 4 files (.ex)
Generated using_private app
iex> UsingPrivate.Bad.hello
hello
:ok 

Works perfectly.

I notice that requireing Logger, for example, works just fine. Would this be something fixed by implementing defmodulep in core Elixir?

@josevalim

This comment has been minimized.

Copy link
Owner

@josevalim josevalim commented Mar 6, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.