Skip to content

Commit

Permalink
Fix submodule implicit alias behavior
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lukaszsamson committed Mar 9, 2023
1 parent 90cc15a commit 2350ccf
Show file tree
Hide file tree
Showing 3 changed files with 271 additions and 41 deletions.
7 changes: 4 additions & 3 deletions lib/elixir_sense/core/metadata_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,12 @@ defmodule ElixirSense.Core.MetadataBuilder do
state =
state
|> maybe_add_protocol_implementation(module)
|> new_namespace(module)
|> add_namespace(module)
|> add_current_module_to_index(position)
|> alias_submodule(module)
|> new_alias_scope
|> new_attributes_scope
|> new_behaviours_scope
|> new_alias_scope
|> new_import_scope
|> new_require_scope
|> new_vars_scope
Expand Down Expand Up @@ -120,7 +121,7 @@ defmodule ElixirSense.Core.MetadataBuilder do
|> remove_import_scope
|> remove_require_scope
|> remove_vars_scope
|> remove_module_from_namespace(module)
|> remove_namespace
|> remove_protocol_implementation
|> result(ast)
end
Expand Down
48 changes: 30 additions & 18 deletions lib/elixir_sense/core/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ defmodule ElixirSense.Core.State do
|> Enum.map(&Module.concat/1)
end

def new_namespace(%__MODULE__{} = state, module) do
def add_namespace(%__MODULE__{} = state, module) do
# TODO refactor to allow {:implementation, protocol, [implementations]} in scope
module = escape_protocol_impementations(module)

Expand Down Expand Up @@ -621,26 +621,11 @@ defmodule ElixirSense.Core.State do
%__MODULE__{state | namespace: [namespace | state.namespace], scopes: [scopes | state.scopes]}
end

def remove_module_from_namespace(%__MODULE__{} = state, module) do
namespace = state.namespace |> hd
module = escape_protocol_impementations(module)
def remove_namespace(%__MODULE__{} = state) do
outer_mods = state.namespace |> tl
outer_scopes = state.scopes |> tl

state = %{state | namespace: outer_mods, scopes: outer_scopes}

if length(outer_scopes) > 1 and state.protocols |> hd == [] and is_list(module) do
# submodule defined, create alias in outer module namespace

# take only outermost submodule part as deeply nested submodules do not create aliases
alias = module |> Enum.take(1) |> Module.concat()
expanded = namespace |> Enum.drop(length(module) - 1) |> Enum.reverse()

state
|> add_alias({alias, expanded})
else
state
end
%{state | namespace: outer_mods, scopes: outer_scopes}
end

def new_named_func(%__MODULE__{} = state, name, arity) do
Expand Down Expand Up @@ -1326,4 +1311,31 @@ defmodule ElixirSense.Core.State do

[current_scope_vars, vars_to_move ++ outer_scope_vars | other_scopes_vars]
end

def alias_submodule(%__MODULE__{} = state, module) do
module = escape_protocol_impementations(module)

if length(state.namespace) > 2 and is_list(module) and state.protocols |> hd == [] do
namespace = state.namespace |> hd

{alias, expanded} =
case module do
[Elixir, alias] ->
# an edge case with external submodule `Elixir.Some`
# this ends up removing unaliasing `Some`
# see https://github.com/elixir-lang/elixir/pull/12451#issuecomment-1461393633
{Module.concat([alias]), [Elixir, alias]}

_ ->
alias = module |> Enum.take(1) |> Module.concat()
expanded = namespace |> Enum.slice((length(module) - 1)..-2) |> Enum.reverse()
{alias, expanded}
end

state
|> add_alias({alias, expanded})
else
state
end
end
end
Loading

0 comments on commit 2350ccf

Please sign in to comment.