Support explicitly marking an alias as used

81 views
Skip to first unread message

Zach Daniel

unread,
May 9, 2022, 1:53:50 PM5/9/22
to elixir-lang-core
This is something coming from a compile time optimization that Ash Framework does.

In an Ash resource there is something called a change its basically like a plug but it operates on an Ash.Changeset

So you might see something like this:
```
# in the resource
actions do
  create :create_with_employee do
    change MyApp.CreateEmployee
  end
end
# the change module
defmodule MyApp.CreateEmployee do
  use Ash.Resource.Change

  def change(changeset, _opts, _context) do
    Ash.Changeset.after_action(changeset, fn _changeset, result ->
       MyApp.Employee.create!(result.stuff, ...)
    end)
  end
end

Now, the change itself, when it comes to the resource, is simple static configuration. It cannot affect the compilation of the resource nor should any thing doing metaprogramming at compile time leverage the internals of that change

Something that changes do often is refer to other related resources, like in this example case. So we drastically increase the surface area for transitive compile time dependencies

Because a runtime dependency in one link becomes a compile time dependency when chained down the road. I.e I depend on the source resource, call it Post at compile time, and Post depends on Employee now at runtime, so I now depend on Employee at compile time.

So to help people with their compile times, I've added some metaprogramming magic that does the following (only in very specific places for specific options) Macro.expand(node, %{env | lexical_tracker: nil}) and it works, no more unnecessary dependency. however, if you do this:
```
alias MyApp.CreateEmployee
create :name do
  change CreateEmployee
end
```
it yells at you for not using the alias, because I just disabled the thing that would inform the compiler that the alias was used

I don't necessarily want to add back in those unnecessary compile time increases, so I'm looking for a way to detect that an alias had been used in these cases, and produce a compiler warning if you didn't add warn: false to the alias, that way you don't get a confusing "alias not used" error, you get (well, I guess you get both) an explanation of why the alias wasn't used and instructions to add warn: false to fix it.


The options I have so far:

1. redefine `alias` and default to `warn: false`
2. redefine `alias` and track which ones have `warn: false` and print a warning if its used in one of these instances, so they can add it
3. if I detect that an alias is used, raise an error at compile time and say that aliases aren't supported here
4. get something in elixir core that allows explicit control to add something to an explicit list of "used aliases"

Looking at the code for the lexical_tracker, it could be as simple as tracking a separate list of explicitly provided modules, or it could be a different mode of reference, i.e `:compile` `:runtime` or `:ignore`, that kind of thing.

Also, if there is another way to accomplish the goal here I'm open to suggestions.

Thanks!

José Valim

unread,
May 9, 2022, 1:56:00 PM5/9/22
to elixir-lang-core
I believe you can do:

    _ = HereIsMyAlias

The compiler won't warn and also will remove the code at the end, so it doesn't affect runtime.

--
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/4992a477-1372-4161-bcf4-0e35a82f154en%40googlegroups.com.

Zach Daniel

unread,
May 9, 2022, 1:56:29 PM5/9/22
to elixir-lang-core
Also, I forgot to mention, it was @icecreamcohen on discord who had the idea that redefining alias may work (although they didn't really condone it), don't want to take credit for anyone elses ideas though.

José Valim

unread,
May 9, 2022, 2:00:11 PM5/9/22
to elixir-lang-core
Btw, we will also have a public API on Elixir v1.14 for expanding literals, so the problem shall disappear altogether. However, you must be extremely careful: this should only be used if you indeed don't use it at compile time.

--
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.

Zach Daniel

unread,
May 9, 2022, 2:02:15 PM5/9/22
to elixir-lang-core
That sounds perfect! Is there any place that I can see what that public API will look like? I totally understand on being careful in that regard. Since Ash DSLs are more like static configuration, there are a few places where this is acceptable, but we don't use it for every (or even most) of the places where a module name might be.

José Valim

unread,
May 9, 2022, 4:10:45 PM5/9/22
to elixir-lang-core

Zach Daniel

unread,
May 9, 2022, 4:33:58 PM5/9/22
to elixir-l...@googlegroups.com
Awesome, thanks!


On Mon, May 09, 2022 at 4:10 PM, José Valim <jose....@dashbit.co> wrote:
It should be added when I fix this: https://github.com/elixir-lang/elixir/issues/11706 :)

To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscribe@googlegroups.com.

--
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-core+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4Lue_uJZdyChHFL_crrW6PVARBFUzm%3DvS5mmPGSSTFi9A%40mail.gmail.com.

Zach Daniel

unread,
Jul 11, 2022, 1:08:02 PM7/11/22
to elixir-lang-core
So I tried out doing the same thing that you are currently doing in `expand_literal/2` and I've hit a snag.

In the Ash DSL, there are some module references where we don't want to incur a runtime dependency *or* a compile time dependency. From what I can tell, the pattern of `expand_literal/2` still incurs runtime dependencies. In Ash, we have this code:

```
  def expand_alias(ast, %Macro.Env{} = env) do
    Macro.prewalk(ast, fn
      {:__aliases__, _, _} = node ->
        Macro.expand(node, %{env | function: {:__ash_placeholder__, 0}})

      other ->
        other
    end)
  end

  def expand_alias_no_require(ast, %Macro.Env{} = env) do
    Macro.prewalk(ast, fn
      {:__aliases__, _, _} = node ->

        Macro.expand(node, %{env | lexical_tracker: nil})

      other ->
        other
    end)
  end
```

which models the difference between how we are currently doing things. The primary issue here is that the things using `expand_alias_no_require/2` currently are marked as unused alias, and from what I can tell `expand_literal/2` doesn't solve for that issue.
On Monday, May 9, 2022 at 4:33:58 PM UTC-4 Zach Daniel wrote:
Awesome, thanks!


On Mon, May 09, 2022 at 4:10 PM, José Valim <jose....@dashbit.co> wrote:
It should be added when I fix this: https://github.com/elixir-lang/elixir/issues/11706 :)

--
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/CAGnRm4Lue_uJZdyChHFL_crrW6PVARBFUzm%3DvS5mmPGSSTFi9A%40mail.gmail.com.

José Valim

unread,
Jul 11, 2022, 1:42:38 PM7/11/22
to elixir-lang-core
In this case you pass lexical_tracker: nil indeed, that's what we do for defimpl for now, although it is a private API for now.

Zach Daniel

unread,
Jul 11, 2022, 1:48:48 PM7/11/22
to elixir-l...@googlegroups.com
Interesting…I'll do some spelunking and try to figure out why `defimpl` doesn't yield the same unused alias warnings that my code does then


```
defmodule Foo.Bar.Baz do
end


defimpl Proto, for: Baz do
  def foo(_thing), do: 10
end
```


On Mon, Jul 11, 2022 at 1:42 PM, José Valim <jose....@dashbit.co> wrote:
In this case you pass lexical_tracker: nil indeed, that's what we do for defimpl for now, although it is a private API for now.

On Mon, Jul 11, 2022 at 7:08 PM Zach Daniel <zachary.s.daniel@gmail.com> wrote:
So I tried out doing the same thing that you are currently doing in `expand_literal/2` and I've hit a snag.

In the Ash DSL, there are some module references where we don't want to incur a runtime dependency *or* a compile time dependency. From what I can tell, the pattern of `expand_literal/2` still incurs runtime dependencies. In Ash, we have this code:

```
  def expand_alias(ast, %Macro.Env{} = env) do
    Macro.prewalk(ast, fn
      {:__aliases__, _, _} = node ->
        Macro.expand(node, %{env | function: {:__ash_placeholder__, 0}})

      other ->
        other
    end)
  end

  def expand_alias_no_require(ast, %Macro.Env{} = env) do
    Macro.prewalk(ast, fn
      {:__aliases__, _, _} = node ->
        Macro.expand(node, %{env | lexical_tracker: nil})

      other ->
        other
    end)
  end
```

which models the difference between how we are currently doing things. The primary issue here is that the things using `expand_alias_no_require/2` currently are marked as unused alias, and from what I can tell `expand_literal/2` doesn't solve for that issue.
On Monday, May 9, 2022 at 4:33:58 PM UTC-4 Zach Daniel wrote:
Awesome, thanks!


To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscribe@googlegroups.com.

--
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-core+unsubscribe@googlegroups.com.

--
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-core+unsubscribe@googlegroups.com.

--
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-core+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JFV6XO-3QsYYTCyFDSCUx%2BZvk8WNWYF3-HTE-ksnuC3Q%40mail.gmail.com.

Zach Daniel

unread,
Jul 11, 2022, 2:34:24 PM7/11/22
to elixir-l...@googlegroups.com
hm… I'm pretty sure that this issue exists for `defimpl` in the new code that you've added.

```
defmodule Foo.Bar.Baz do
end


defimpl Proto, for: Baz do
  def foo(_thing), do: 10
end

```
will show an `unused alias Baz` warning.


On Mon, Jul 11, 2022 at 1:48 PM, Zach Daniel <zachary....@gmail.com> wrote:
Interesting…I'll do some spelunking and try to figure out why `defimpl` doesn't yield the same unused alias warnings that my code does then


```
defmodule Foo.Bar.Baz do
end


defimpl Proto, for: Baz do
  def foo(_thing), do: 10
end
```


José Valim

unread,
Jul 11, 2022, 2:37:21 PM7/11/22
to elixir-lang-core
I see. Great find. Back to the drawing board.

On Mon, Jul 11, 2022 at 8:34 PM Zach Daniel <zachary....@gmail.com> wrote:
hm… I'm pretty sure that this issue exists for `defimpl` in the new code that you've added.

```
defmodule Foo.Bar.Baz do
end


defimpl Proto, for: Baz do
  def foo(_thing), do: 10
end

```
will show an `unused alias Baz` warning.


On Mon, Jul 11, 2022 at 1:48 PM, Zach Daniel <zachary....@gmail.com> wrote:
Interesting…I'll do some spelunking and try to figure out why `defimpl` doesn't yield the same unused alias warnings that my code does then


```
defmodule Foo.Bar.Baz do
end


defimpl Proto, for: Baz do
  def foo(_thing), do: 10
end
```


On Mon, Jul 11, 2022 at 1:42 PM, José Valim <jose....@dashbit.co> wrote:
In this case you pass lexical_tracker: nil indeed, that's what we do for defimpl for now, although it is a private API for now.

--
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.

--
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.

--
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.

--
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.

José Valim

unread,
Jul 11, 2022, 2:39:46 PM7/11/22
to elixir-lang-core
The best bet we can have at the moment is to add a runtime dependency indeed... why would a runtime dependency be bad in your case?

Zach Daniel

unread,
Jul 11, 2022, 3:06:45 PM7/11/22
to elixir-l...@googlegroups.com
Runtime dependencies are unideal in this case because of transitive compile time dependencies. An example of what we use this for is for what Ash calls a "change", which is effectively a `plug` but for changeset modifiers. i.e

```
defmodule DoSomething do
  use Ash.Resource.Change

  def change(changeset, _, _) do
    …do something with the changeset
  end
end
```

So now if someone has a compile time dependency against a resource (or any resource that in any way relates back to that resource, because relationships create runtime dependencies), then they have to recompile. However, Ash resources are declarative/static configuration, and so the idea is that they are *meant* to be usable at compile time/for meta-programming. At no point does the resource DSL module actually care about the module you've configured for things like this, it will only ever matter at runtime when it gets called.



On Mon, Jul 11, 2022 at 2:39 PM, José Valim <jose....@dashbit.co> wrote:
The best bet we can have at the moment is to add a runtime dependency indeed... why would a runtime dependency be bad in your case?

On Mon, Jul 11, 2022 at 8:37 PM José Valim <jose.valim@dashbit.co> wrote:
I see. Great find. Back to the drawing board.

To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscribe@googlegroups.com.

--
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-core+unsubscribe@googlegroups.com.

--
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-core+unsubscribe@googlegroups.com.

--
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-core+unsubscribe@googlegroups.com.

--
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-core+unsubscribe@googlegroups.com.

--
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-core+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4KoK-HHxniugy%2BjGdcUXKEF%2Bmv61XR4hmcHEEXAe2v2cw%40mail.gmail.com.

Reply all
Reply to author
Forward
0 new messages