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

Warn about name conflicts in nested modules #12451

Closed
wants to merge 4 commits into from
Closed

Conversation

whatyouhide
Copy link
Member

@whatyouhide whatyouhide commented Mar 6, 2023

Closes #11130.

I’m not sure what to do for this particular warning in this particular test, since we're testing exactly the behaviour that warns here 😕

warning: the nested call to "defmodule First" will not implicitly alias First here because there is already an alias Some.First that resolves to the same alias. To fix this, either remove the Some.First alias, or define the nested module outside of the parent
  test/elixir/kernel/alias_test.exs:132: Macro.AliasTest.User (module)

@josevalim
Copy link
Member

I’m not sure what to do for this particular warning in this particular test, since we're testing exactly the behaviour that warns here 😕

Move whatever that module is checking into the same test that checks for the warning :)

@whatyouhide
Copy link
Member Author

@josevalim done and done ✅

@@ -129,14 +137,25 @@ defmodule Macro.AliasTest.Aliaser do
end
end

defmodule Macro.AliasTest.User do
defmodule Macro.AliasTest do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is most likely no longer needs to be a separate module, we can fold into the previous one?

"the nested call to \"defmodule #{defmod_name}\" will not implicitly alias " <>
"#{inspect(alias)} here because there is already an alias #{inspect(other_parent)} " <>
"that resolves to the same alias. To fix this, either remove the " <>
"#{inspect(other_parent)} alias, or define the nested module outside of the parent",
Copy link
Member

@josevalim josevalim Mar 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to think about the error message. What do you think about:

you are defining a module named Child.Comment, inside module Post,
but the module will be defined as RootChild.Comment instead of Post.Child.Comment
due to the "alias RootChild, as: Child". You must address this ambiguity
either by removing the alias Child or by defining the module outside of the parent

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's not what happens though, because there is technically no ambiguity, is just that we don't set up an implicit alias. I updated the error message, I like it a lot better now. If you're good with it, I'll merge.

Copy link
Member

@josevalim josevalim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nits and ship it!

@lukaszsamson
Copy link
Contributor

Either I fail to understand what's the real issue here or those error messages are not correct.
Tested in iex on master
Code without conflicts:

defmodule Parent do
        alias OtherParent.OtherChild
        def a1, do: IO.puts "Parent " <> inspect(__ENV__.aliases)
        defmodule Child do
          def a2, do: IO.puts "Parent.Child " <> inspect(__ENV__.aliases)
        end
        def a3, do: IO.puts "Parent " <> inspect(__ENV__.aliases)
end

gives

iex(2)> Parent.a1                                                                
Parent [{OtherChild, OtherParent.OtherChild}]
:ok
iex(3)> Parent.Child.a2
Parent.Child [{OtherChild, OtherParent.OtherChild}, {Child, Parent.Child}]
:ok
iex(4)> Parent.a3      
Parent [{OtherChild, OtherParent.OtherChild}, {Child, Parent.Child}]
:ok

and code with conflicts

defmodule Parent do
        alias OtherParent.Child
        def a1, do: IO.puts "Parent " <> inspect(__ENV__.aliases)
        defmodule Child do
          def a2, do: IO.puts "Parent.Child " <> inspect(__ENV__.aliases)
        end
        def a3, do: IO.puts "Parent " <> inspect(__ENV__.aliases)
end

gives

iex(2)> Parent.a1
Parent [{Child, OtherParent.Child}]
:ok
iex(3)> Parent.Child.a2
Parent.Child [{Child, Parent.Child}]
:ok
iex(4)> Parent.a3      
Parent [{Child, Parent.Child}]
:ok

I'm seeing that the implicit alias Parent.Child, as: Child is set both in the submodule and in the parent module. The only difference is that alias OtherParent.Child, as: Child gets overwritten.

Regarding the messages, it's not clear where this implicit alias is not being set. Is it in the parent module body or in the child module body or both

@josevalim
Copy link
Member

The only difference is that alias OtherParent.Child, as: Child gets overwritten.

The implicit overriding is what we are warning about.

@josevalim
Copy link
Member

Ah, I see your point, the original report says the alias is not defined, but the alias is always defined and overrides. Which I would say is the correct behaviour! So I think we can go ahead and close this?

Thanks for sanity checking @lukaszsamson!

@lukaszsamson
Copy link
Contributor

The original problem from the discussion list was aboutan ecto issue. Maybe ecto macros make some assumptions about aliases that are not fully correct

@josevalim
Copy link
Member

josevalim commented Mar 8, 2023

If we want, we can add a warning that a previously defined alias is being overridden by the module.

module Child.Comment, inside module Post, is redefining the alias Child to point
to Post.Child instead of RootChild. Either remove the alias to RootChild or define
the module outside of its parent

But it feels like it is expected new aliases override old aliases, so I would close it.

@josevalim
Copy link
Member

Yes, Ecto is showing the behaviour we thought we were seeing in this pull request. But not Elixir.

@josevalim
Copy link
Member

I will close this. I think the overridde warning is ok. Later aliases always override earlier ones. Sorry @whatyouhide!

@josevalim josevalim closed this Mar 8, 2023
@lukaszsamson
Copy link
Contributor

But it feels like it is expected new aliases override old aliases

That's easy to understand in case of ordinary aliases. defmodule implicit ones are not so obvious (case in point - I've found another bug in elixir_sense alias handling)

@josevalim josevalim reopened this Mar 8, 2023
@whatyouhide
Copy link
Member Author

@josevalim yeah I think the behaviour is correct per se, but it's hard to understand that the defmodule overrides the alias. So, do we still want this? If yes, should I update the error message?

@josevalim
Copy link
Member

Yeah, let's give it a try with an updated message. And we can run on Elixir main for a couple weeks before release and see if we catch anything in any of our existing projects.

@lukaszsamson
Copy link
Contributor

I wanted to see how this alias overwriting work in case of external submodules and I've found something odd

defmodule Parent do
        alias OtherParent.Child
        def a1, do: IO.puts "Parent " <> inspect(__ENV__.aliases)
        defmodule Elixir.Child do
          def a2, do: IO.puts "Elixir.Child " <> inspect(__ENV__.aliases)
        end
        def a3, do: IO.puts "Parent " <> inspect(__ENV__.aliases)
end
iex(3)> Parent.a1
Parent [{Child, OtherParent.Child}]
:ok
iex(4)> Child.a2
Elixir.Child []
:ok
iex(5)> Parent.a3
Parent []
:ok

So it removes the alias. This does not happen for nested external submodule e.g. Elixir.Child.One

@josevalim
Copy link
Member

I think that makes sense. Otherwise you wouldn't be able to refer to it as Child in the same module.

@lukaszsamson
Copy link
Contributor

I think that makes sense. Otherwise you wouldn't be able to refer to it as Child in the same module.

OK but why there's no similar behavior in this case?

defmodule Parent do
        alias OtherParent.Child
        def a1, do: IO.puts "Parent " <> inspect(__ENV__.aliases)
        defmodule Elixir.Child.Some do
          def a2, do: IO.puts "Elixir.Child.Some " <> inspect(__ENV__.aliases)
        end
        def a3, do: IO.puts "Parent " <> inspect(__ENV__.aliases)
end
iex(2)> Parent.a1
Parent [{Child, OtherParent.Child}]
:ok
iex(3)> Child.Some.a2
Elixir.Child.Some [{Child, OtherParent.Child}]
:ok
iex(3)> Parent.a3
Parent [{Child, OtherParent.Child}]
:ok

A nested external submodule does not unalias Child. I'm not saying it's a bug, just a non obvious edge case.

@josevalim
Copy link
Member

Ok, we should either do it for both or non. This is a bug.

lukaszsamson added a commit to elixir-lsp/elixir_sense that referenced this pull request Mar 9, 2023
submodule now properly sets alias in its own scope (introduced in 9de7dd8)
submodule overwrites alias
external submodule unaliases
See discussions in elixir-lang/elixir#12451
lukaszsamson added a commit to elixir-lsp/elixir_sense that referenced this pull request Mar 10, 2023
submodule now properly sets alias in its own scope (introduced in 9de7dd8)
submodule overwrites alias
external submodule unaliases
See discussions in elixir-lang/elixir#12451
@whatyouhide
Copy link
Member Author

@josevalim so, do we still want the warning in this PR, or should we close this? 🙃

@josevalim
Copy link
Member

I think we can close this and we have added a warning to Ecto. Sorry for the false alarm here.

@josevalim josevalim closed this Mar 22, 2023
@whatyouhide whatyouhide deleted the al/alias-warning branch March 23, 2023 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Warn about defining implicit alias modules with aliases that are taken
3 participants