[Proposal] Add `:generated` field to `Macro.Env`

34 views
Skip to first unread message

luk...@niemier.pl

unread,
Sep 28, 2021, 3:07:33 AM9/28/21
to elixir-lang-core
Currently there is no way to check whether the current environment is something that user have written or it is generated by macro. There is technical workaround with checking `env.line == 0`, but IIRC it will not work when `location: :keep` is set. The reason for that is to be able to detect call to a function that is within code generated by macro. This is come up from my discussion with Jason Alexon on Slack when he was trying to implement https://github.com/axelson/priv_check which checks for function calls to the private (aka marked as `@doc false`) functions.

Such field could be a boolean, `mfa()` of the current macro, or even list of macro invocations. Any of these would be a helpful. I think the latter 2 options are most appealing, as it will allow for better control over the current context, so we could warn about calls from user-defined macros vs library-defined macros.

José Valim

unread,
Sep 28, 2021, 3:39:48 AM9/28/21
to elixir-l...@googlegroups.com
> This is come up from my discussion with Jason Alexon on Slack when he was trying to implement https://github.com/axelson/priv_check which checks for function calls to the private (aka marked as `@doc false`) functions.

My understanding is that you want to not warn on calls to private functions, as long as they are generated. If this is the case, the generated information is part of the metadata and the metadata is available in compilation tracers, which is what I would most likely use to trace all calls.

> Such field could be a boolean, `mfa()` of the current macro, or even list of macro invocations. Any of these would be a helpful. I think the latter 2 options are most appealing, as it will allow for better control over the current context

If you need this, then we'd need to expand :generated to store a module. A module most likely provides enough boundary, I can't see a function being ok to be called from SomeMod.a/1 but not SomeMod.b/1.

Wiebe-Marten Wijnja

unread,
Sep 28, 2021, 3:53:50 AM9/28/21
to elixir-l...@googlegroups.com
I think that something in this field would be a nice addition.
While writing macro-heavy code myself I frequently have encountered
situations in which warnings generated by the compiler, linter,
dialyzer, or other tooling did not make much sense as the code was
generated and not user-written.
However, currently even with `generated: true` only a select few
warnings are turned off, and external tooling has no clue whether they
should or should not generate their warnings.

~Marten/Qqwy

OpenPGP_signature

Łukasz Niemier

unread,
Sep 28, 2021, 4:01:41 AM9/28/21
to elixir-l...@googlegroups.com
> > This is come up from my discussion with Jason Alexon on Slack when he was trying to implement https://github.com/axelson/priv_check which checks for function calls to the private (aka marked as `@doc false`) functions.
>
> My understanding is that you want to not warn on calls to private functions, as long as they are generated. If this is the case, the generated information is part of the metadata and the metadata is available in compilation tracers, which is what I would most likely use to trace all calls.

I do not see such information in compiler trace metadata

iex(3)> Code.compile_quoted(quote do
...(3)> defmodule Foo do
...(3)> require Logger
...(3)>
...(3)> def test do
...(3)> Logger.info("foo")
...(3)> end
...(3)> end
...(3)> end)
{:remote_macro, [required: true, context: Elixir, import: Kernel], Kernel,
:defmodule, 2}
{:remote_function, [], :elixir_module, :compile, 4}
{:remote_function, [], Kernel.LexicalTracker, :read_cache, 2}
{:remote_macro, [required: true, context: Elixir, import: Kernel], Kernel, :def,
2}
{:remote_function, [], :elixir_def, :store_definition, 5}
{:remote_function, [], :elixir_module, :read_cache, 2}
{:remote_function, [], :elixir_utils, :noop, 0}
{:remote_macro, [], Logger, :info, 1}
{:remote_function, [], Logger, :__should_log__, 2}
{:remote_function, [], Logger, :__do_log__, 4}

As you can see, there is no way to detect that `Logger.__should_log__/2` and `Logger.__do_log__/4` are called from generated context, and these functions are quite private.

The same goes for internal Elixir functions like `:elixir_module.compile/4` or `Kernel.LexicalTracker.read_cache/2`. Being able to check that these functions weren't called directly by user would make implementation of such tracer much easier.

--

Łukasz Niemier
luk...@niemier.pl

José Valim

unread,
Sep 28, 2021, 5:58:09 AM9/28/21
to elixir-l...@googlegroups.com
> As you can see, there is no way to detect that `Logger.__should_log__/2` and `Logger.__do_log__/4` are called from generated context, and these functions are quite private.

Those macros do not add generated: true to their quoted expression. However, based on your examples, it seems you don't want something necessarily related to the `generated: true` in quoted expressions (which is why examples are important in said discussions!).

It seems what you want is something that says: "this came from a quote". But keep in mind that, in general, it would be very hard to keep a list of all quoted expressions nestings so far, because almost all macros mix quoted code with unquoted code. The best we can do is to add some annotation in the AST from `quote do...end`, but that will also lead to false positives from macros that are building parts of the AST by hand.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/0C188167-01D6-4BE3-83DF-3147FDACEE30%40niemier.pl.
Reply all
Reply to author
Forward
0 new messages