Skip to content

Commit

Permalink
expand before_compile
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszsamson committed Aug 13, 2024
1 parent 0b805ad commit ac81dcb
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 28 deletions.
28 changes: 24 additions & 4 deletions lib/elixir_sense/core/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1163,7 +1163,7 @@ defmodule ElixirSense.Core.Compiler do

state =
state
|> add_attribute(name, inferred_type, is_definition, meta)
|> add_attribute(env, name, meta, e_args, inferred_type, is_definition)
|> add_current_env_to_line(meta, env)

{{:@, meta, [{name, name_meta, e_args}]}, state, env}
Expand Down Expand Up @@ -1475,7 +1475,26 @@ defmodule ElixirSense.Core.Compiler do

{state, _env} = maybe_add_protocol_behaviour(state, %{env | module: full})

{result, state, _env} = expand(block, state, %{env | module: full})
{result, state, e_env} = expand(block, state, %{env | module: full})

before_compile =
for args <- Map.get(state.attribute_store, {full, :before_compile}, []) do
target =
case args do
{module, fun} -> [module, fun]
module -> [module, :__before_compile__]
end

{:__block__, [],
[
{:require, [], [hd(target)]},
{{:., [], target}, [], [e_env]}
]}
end

module_callbacks = {:__block__, [], before_compile}

{_result, state, _e_env} = expand(module_callbacks, state, e_env)

state =
state
Expand Down Expand Up @@ -1504,8 +1523,9 @@ defmodule ElixirSense.Core.Compiler do
|> remove_attributes_scope
|> remove_module

# TODO hardcode expansion?
# to result of require (a module atom) and :elixir_module.compile dot call in block
# in elixir the result of defmodule expansion is
# require (a module atom) and :elixir_module.compile dot call in block
# we don't need that

{{:__block__, [], []}, state, env}
end
Expand Down
74 changes: 59 additions & 15 deletions lib/elixir_sense/core/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ defmodule ElixirSense.Core.State do
optional_callbacks_context: list(),
lines_to_env: lines_to_env_t,
cursor_env: nil | {keyword, ElixirSense.Core.State.Env.t()},
ex_unit_describe: nil | atom
ex_unit_describe: nil | atom,
attribute_store: %{optional(module) => term}
}

defstruct attributes: [[]],
Expand Down Expand Up @@ -98,7 +99,8 @@ defmodule ElixirSense.Core.State do
optional_callbacks_context: [[]],
lines_to_env: %{},
cursor_env: nil,
ex_unit_describe: nil
ex_unit_describe: nil,
attribute_store: %{}

defmodule Env do
@moduledoc """
Expand Down Expand Up @@ -515,8 +517,7 @@ defmodule ElixirSense.Core.State do
doc,
meta,
options
)
when is_tuple(position) do
) do
current_info = Map.get(state.mods_funs_to_positions, {module, fun, arity}, %ModFunInfo{})
current_params = current_info |> Map.get(:params, [])
current_positions = current_info |> Map.get(:positions, [])
Expand Down Expand Up @@ -980,10 +981,7 @@ defmodule ElixirSense.Core.State do

def add_var_read(%__MODULE__{} = state, _), do: state

@builtin_attributes ElixirSense.Core.BuiltinAttributes.all()

def add_attribute(%__MODULE__{} = state, attribute, type, is_definition, meta)
when attribute not in @builtin_attributes do
def add_attribute(%__MODULE__{} = state, env, attribute, meta, args, type, is_definition) do
position = extract_position(meta)
[attributes_from_scope | other_attributes] = state.attributes

Expand Down Expand Up @@ -1023,11 +1021,47 @@ defmodule ElixirSense.Core.State do

attributes = [attributes_from_scope | other_attributes]
scope_attributes = [attributes_from_scope | tl(state.scope_attributes)]
%__MODULE__{state | attributes: attributes, scope_attributes: scope_attributes}
end

def add_attribute(%__MODULE__{} = state, _attribute, _type, _is_definition, _meta) do
state
# TODO handle other
# {moduledoc, nil, nil, []},
# {after_compile, [], accumulate, []},
# {after_verify, [], accumulate, []},
# {before_compile, [], accumulate, []},
# {behaviour, [], accumulate, []},
# {compile, [], accumulate, []},
# {derive, [], accumulate, []},
# {dialyzer, [], accumulate, []},
# {external_resource, [], accumulate, []},
# {on_definition, [], accumulate, []},
# {type, [], accumulate, []},
# {opaque, [], accumulate, []},
# {typep, [], accumulate, []},
# {spec, [], accumulate, []},
# {callback, [], accumulate, []},
# {macrocallback, [], accumulate, []},
# {optional_callbacks, [], accumulate, []},
accumulating? =
attribute in [:before_compile, :after_compile, :after_verify, :on_definition, :on_load]

attribute_store =
if is_definition do
[arg] = args

if accumulating? do
state.attribute_store |> Map.update({env.module, attribute}, [arg], &(&1 ++ [arg]))
else
state.attribute_store |> Map.put({env.module, attribute}, arg)
end
else
state.attribute_store
end

%__MODULE__{
state
| attributes: attributes,
scope_attributes: scope_attributes,
attribute_store: attribute_store
}
end

def add_behaviour(module, %__MODULE__{} = state, env) when is_atom(module) do
Expand Down Expand Up @@ -1173,8 +1207,13 @@ defmodule ElixirSense.Core.State do
]

def add_module_functions(state, env, functions, range) do
{{line, column}, _} = range
meta = [line: line || 0] ++ if(column > 0, do: [column: column], else: [])
{line, column} =
case range do
{{line, column}, _} -> {line, column}
_ -> {0, nil}
end

meta = [line: line] ++ if(column > 0, do: [column: column], else: [])

(functions ++ @module_functions)
|> Enum.reduce(state, fn {name, args, kind}, acc ->
Expand All @@ -1197,7 +1236,12 @@ defmodule ElixirSense.Core.State do
end

def add_struct_or_exception(state, env, type, fields, range) do
{{line, column}, _} = range
{line, column} =
case range do
{{line, column}, _} -> {line, column}
_ -> {0, nil}
end

meta = [line: line || 0] ++ if(column > 0, do: [column: column], else: [])

fields =
Expand Down
53 changes: 44 additions & 9 deletions test/elixir_sense/core/metadata_builder_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4245,26 +4245,34 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
defmacro __using__(_options) do
quote do
alias Some.First
IO.inspect({__ENV__.aliases, __ENV__.macro_aliases})
end
end
end

test "macro alias" do
test "macro alias does not leak outside macro" do
state =
"""
defmodule MyModule do
use ElixirSense.Core.MetadataBuilderTest.Macro.AliasTest.Definer
use ElixirSense.Core.MetadataBuilderTest.Macro.AliasTest.Aliaser
IO.puts ""
a = %MyModule.First{}
b = %MyModule.Second{}
end
"""
|> string_to_state

assert [{First, {_, Some.First}}] = state.lines_to_env[4].macro_aliases
# TODO should we handle @before_compile?

assert %{
MyModule.First => %StructInfo{
fields: [foo: :bar, __struct__: MyModule.First]
},
MyModule.Second => %StructInfo{
fields: [
baz: {:%, [], [MyModule.First, {:%{}, [], [{:foo, :bar}]}]},
__struct__: MyModule.Second
]
}
} = state.structs
end
end

Expand Down Expand Up @@ -5434,7 +5442,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
use Application
@behaviour SomeModule.SomeBehaviour
IO.puts ""
defmodule InnerModuleWithUse do
defmodule InnerModuleWithUse1 do
use GenServer
IO.puts ""
end
Expand Down Expand Up @@ -5997,6 +6005,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
])

assert [
%AttributeInfo{name: :before_compile, positions: [{2, _}]},
%AttributeInfo{name: :my_attribute, positions: [{2, _}]}
] = get_line_attributes(state, 4)

Expand Down Expand Up @@ -7294,7 +7303,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
test "registers calls on ex_unit DSL" do
state =
"""
defmodule MyModuleTest do
defmodule MyModuleTests do
use ExUnit.Case
describe "describe1" do
Expand Down Expand Up @@ -7664,7 +7673,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
test "gets ExUnit imports from `use ExUnit.Case`" do
state =
"""
defmodule MyTest do
defmodule MyModuleTest do
use ExUnit.Case
IO.puts ""
end
Expand All @@ -7678,7 +7687,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
test "gets ExUnit imports from case template" do
state =
"""
defmodule MyTest do
defmodule My1Test do
use ElixirSenseExample.CaseTemplateExample
IO.puts ""
end
Expand Down Expand Up @@ -8421,6 +8430,32 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
assert state
end

describe "module callbacks" do
defmodule Callbacks do
defmacro __before_compile__(_arg) do
quote do
def constant, do: 1
defoverridable constant: 0
end
end
end

test "before_compile" do
state =
"""
defmodule User do
@before_compile ElixirSense.Core.MetadataBuilderTest.Callbacks
end
"""
|> string_to_state

assert %ModFunInfo{meta: %{overridable: true}} =
state.mods_funs_to_positions[{User, :constant, 0}]
end

# TODO after_compile?, after_verify?, on_defined, on_load?
end

defp string_to_state(string) do
string
|> Code.string_to_quoted(columns: true, token_metadata: true)
Expand Down

0 comments on commit ac81dcb

Please sign in to comment.