Skip to content

Commit 7752b1b

Browse files
committed
collect reference on alias
1 parent f248030 commit 7752b1b

File tree

8 files changed

+254
-52
lines changed

8 files changed

+254
-52
lines changed

lib/elixir_sense/core/compiler.ex

+119-33
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,60 @@ defmodule ElixirSense.Core.Compiler do
88
alias ElixirSense.Core.Normalized.Macro.Env, as: NormalizedMacroEnv
99
alias ElixirSense.Core.State.ModFunInfo
1010

11-
@env :elixir_env.new()
12-
def env, do: @env
11+
@trace_key :elixir_sense_trace
12+
13+
def trace(event, env) do
14+
trace = get_trace()
15+
Process.put(@trace_key, [{event, env} | trace])
16+
:ok
17+
end
18+
19+
defp get_trace do
20+
Process.get(@trace_key, [])
21+
end
22+
23+
defp clear_trace do
24+
Process.delete(@trace_key)
25+
end
26+
27+
def collect_traces(state) do
28+
trace = get_trace()
29+
30+
state =
31+
Enum.reduce(trace, state, fn {event, env}, acc ->
32+
case event do
33+
{:alias_reference, meta, alias} ->
34+
# emitted by Macro.expand/2 and Macro.expand_once/2
35+
acc
36+
|> State.add_call_to_line({alias, nil, nil}, meta)
37+
|> State.add_current_env_to_line(meta, env)
38+
39+
{:alias, meta, module, _as, _opts} ->
40+
# emitted by Macro.Env.define_alias/3 and Macro.Env.define_require/3
41+
acc
42+
|> State.add_call_to_line({module, nil, nil}, meta)
43+
|> State.add_current_env_to_line(meta, env)
44+
45+
{kind, meta, module, _opts} when kind in [:import, :require] ->
46+
# emitted by Macro.Env.define_import/3 and Macro.Env.define_require/3
47+
acc
48+
|> State.add_call_to_line({module, nil, nil}, meta)
49+
|> State.add_current_env_to_line(meta, env)
50+
51+
_ ->
52+
Logger.warning("Unhandled trace event: #{inspect(event)}")
53+
acc
54+
end
55+
end)
56+
57+
clear_trace()
58+
state
59+
end
60+
61+
def env do
62+
env = :elixir_env.new()
63+
%{env | tracers: [__MODULE__]}
64+
end
1365

1466
def expand(ast, state, env) do
1567
try do
@@ -116,23 +168,8 @@ defmodule ElixirSense.Core.Compiler do
116168

117169
# __aliases__
118170

119-
defp do_expand({:__aliases__, meta, [head | tail] = list}, state, env) do
120-
case NormalizedMacroEnv.expand_alias(env, meta, list, trace: false) do
121-
{:alias, alias} ->
122-
# TODO track alias
123-
{alias, state, env}
124-
125-
:error ->
126-
{head, state, env} = expand(head, state, env)
127-
128-
if is_atom(head) do
129-
# TODO track alias
130-
{Module.concat([head | tail]), state, env}
131-
else
132-
# elixir raises here invalid_alias
133-
{{:__aliases__, meta, [head | tail]}, state, env}
134-
end
135-
end
171+
defp do_expand({:__aliases__, _, _} = alias, state, env) do
172+
expand_aliases(alias, state, env, true)
136173
end
137174

138175
# require, alias, import
@@ -161,13 +198,13 @@ defmodule ElixirSense.Core.Compiler do
161198
|> State.add_first_alias_positions(env, meta)
162199
|> State.add_current_env_to_line(meta, env)
163200

164-
# no need to call expand_without_aliases_report - we never report
165-
{arg, state, env} = expand(arg, state, env)
201+
{arg, state, env} = expand_without_aliases_report(arg, state, env)
166202
{opts, state, env} = expand_opts([:as, :warn], no_alias_opts(opts), state, env)
167203

168204
if is_atom(arg) do
169-
case NormalizedMacroEnv.define_alias(env, meta, arg, [trace: false] ++ opts) do
205+
case NormalizedMacroEnv.define_alias(env, meta, arg, [trace: true] ++ opts) do
170206
{:ok, env} ->
207+
state = collect_traces(state)
171208
{arg, state, env}
172209

173210
{:error, _} ->
@@ -185,8 +222,7 @@ defmodule ElixirSense.Core.Compiler do
185222
state
186223
|> State.add_current_env_to_line(meta, env)
187224

188-
# no need to call expand_without_aliases_report - we never report
189-
{arg, state, env} = expand(arg, state, env)
225+
{arg, state, env} = expand_without_aliases_report(arg, state, env)
190226

191227
{opts, state, env} =
192228
expand_opts([:as, :warn], no_alias_opts(opts), state, env)
@@ -197,8 +233,9 @@ defmodule ElixirSense.Core.Compiler do
197233
if is_atom(arg) do
198234
# elixir calls here :elixir_aliases.ensure_loaded(meta, e_ref, et)
199235
# and optionally waits until required module is compiled
200-
case NormalizedMacroEnv.define_require(env, meta, arg, [trace: false] ++ opts) do
236+
case NormalizedMacroEnv.define_require(env, meta, arg, [trace: true] ++ opts) do
201237
{:ok, env} ->
238+
state = collect_traces(state)
202239
{arg, state, env}
203240

204241
{:error, _} ->
@@ -216,21 +253,21 @@ defmodule ElixirSense.Core.Compiler do
216253
state
217254
|> State.add_current_env_to_line(meta, env)
218255

219-
# no need to call expand_without_aliases_report - we never report
220-
{arg, state, env} = expand(arg, state, env)
256+
{arg, state, env} = expand_without_aliases_report(arg, state, env)
221257
{opts, state, env} = expand_opts([:only, :except, :warn], opts, state, env)
222258

223259
if is_atom(arg) do
224260
opts =
225261
opts
226262
|> Keyword.merge(
227-
trace: false,
263+
trace: true,
228264
emit_warnings: false,
229265
info_callback: import_info_callback(arg, state)
230266
)
231267

232268
case NormalizedMacroEnv.define_import(env, meta, arg, opts) do
233269
{:ok, env} ->
270+
state = collect_traces(state)
234271
{arg, state, env}
235272

236273
_ ->
@@ -274,15 +311,15 @@ defmodule ElixirSense.Core.Compiler do
274311
# elixir checks if context is not match
275312
state = State.add_current_env_to_line(state, meta, env)
276313

277-
{escape_map(escape_env_entries(meta, state, env)), state, env}
314+
{escape_map(escape_env_entries(meta, state, %{env | tracers: []})), state, env}
278315
end
279316

280317
defp do_expand({{:., dot_meta, [{:__ENV__, meta, atom}, field]}, call_meta, []}, s, e)
281318
when is_atom(atom) and is_atom(field) do
282319
# elixir checks if context is not match
283320
s = State.add_current_env_to_line(s, call_meta, e)
284321

285-
env = escape_env_entries(meta, s, e)
322+
env = escape_env_entries(meta, s, %{e | tracers: []})
286323

287324
case Map.fetch(env, field) do
288325
{:ok, value} -> {value, s, e}
@@ -1527,6 +1564,8 @@ defmodule ElixirSense.Core.Compiler do
15271564
function: {:__impl__, 1}
15281565
})
15291566

1567+
state = collect_traces(state)
1568+
15301569
{for, state} =
15311570
if is_atom(for) or (is_list(for) and Enum.all?(for, &is_atom/1)) do
15321571
{for, state}
@@ -1602,6 +1641,8 @@ defmodule ElixirSense.Core.Compiler do
16021641
{:"Elixir.__Unknown__", env}
16031642
end
16041643

1644+
state = collect_traces(state)
1645+
16051646
# elixir emits a special require directive with :defined key set in meta
16061647
# require expand does alias, updates context_modules and runtime_modules
16071648
# we do it here instead
@@ -2088,7 +2129,7 @@ defmodule ElixirSense.Core.Compiler do
20882129
# see https://github.com/elixir-lang/elixir/pull/12451#issuecomment-1461393633
20892130
defp alias_defmodule({:__aliases__, meta, [:"Elixir", t] = x}, module, env) do
20902131
alias = String.to_atom("Elixir." <> Atom.to_string(t))
2091-
{:ok, env} = NormalizedMacroEnv.define_alias(env, meta, alias, as: alias, trace: false)
2132+
{:ok, env} = NormalizedMacroEnv.define_alias(env, meta, alias, as: alias, trace: true)
20922133
{module, env}
20932134
end
20942135
end
@@ -2103,7 +2144,7 @@ defmodule ElixirSense.Core.Compiler do
21032144
defp alias_defmodule({:__aliases__, meta, [h | t]}, _module, env) when is_atom(h) do
21042145
module = Module.concat([env.module, h])
21052146
alias = String.to_atom("Elixir." <> Atom.to_string(h))
2106-
{:ok, env} = NormalizedMacroEnv.define_alias(env, meta, module, as: alias, trace: false)
2147+
{:ok, env} = NormalizedMacroEnv.define_alias(env, meta, module, as: alias, trace: true)
21072148

21082149
case t do
21092150
[] -> {module, env}
@@ -2334,7 +2375,7 @@ defmodule ElixirSense.Core.Compiler do
23342375
# end
23352376

23362377
defp expand_multi_alias_call(kind, meta, base, refs, opts, state, env) do
2337-
{base_ref, state, env} = expand(base, state, env)
2378+
{base_ref, state, env} = expand_without_aliases_report(base, state, env)
23382379

23392380
fun = fn
23402381
{:__aliases__, _, ref}, state, env ->
@@ -2730,6 +2771,51 @@ defmodule ElixirSense.Core.Compiler do
27302771
@internals [{:module_info, 1}, {:module_info, 0}]
27312772
end
27322773

2774+
defp expand_without_aliases_report({:__aliases__, _, _} = alias, state, env) do
2775+
expand_aliases(alias, state, env, false)
2776+
end
2777+
2778+
defp expand_without_aliases_report(other, state, env) do
2779+
expand(other, state, env)
2780+
end
2781+
2782+
defp expand_aliases({:__aliases__, meta, [head | tail] = list}, state, env, report) do
2783+
case NormalizedMacroEnv.expand_alias(env, meta, list, trace: false) do
2784+
{:alias, alias} ->
2785+
state =
2786+
if report do
2787+
state
2788+
|> State.add_call_to_line({alias, nil, nil}, meta)
2789+
|> State.add_current_env_to_line(meta, env)
2790+
else
2791+
state
2792+
end
2793+
2794+
{alias, state, env}
2795+
2796+
:error ->
2797+
{head, state, env} = expand(head, state, env)
2798+
2799+
if is_atom(head) do
2800+
alias = Module.concat([head | tail])
2801+
2802+
state =
2803+
if report do
2804+
state
2805+
|> State.add_call_to_line({alias, nil, nil}, meta)
2806+
|> State.add_current_env_to_line(meta, env)
2807+
else
2808+
state
2809+
end
2810+
2811+
{alias, state, env}
2812+
else
2813+
# elixir raises here invalid_alias
2814+
{{:__aliases__, meta, [head | tail]}, state, env}
2815+
end
2816+
end
2817+
end
2818+
27332819
defp import_info_callback(module, state) do
27342820
fn kind ->
27352821
if Map.has_key?(state.mods_funs_to_positions, {module, nil, nil}) do

lib/elixir_sense/core/compiler/bitstring.ex

+4-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,10 @@ defmodule ElixirSense.Core.Compiler.Bitstring do
195195
end
196196

197197
# TODO how to check for cursor here?
198-
case Compiler.Macro.expand(ha, Map.put(e, :line, Utils.get_line(meta))) do
198+
expanded = Compiler.Macro.expand(ha, Map.put(e, :line, Utils.get_line(meta)))
199+
s = Compiler.collect_traces(s)
200+
201+
case expanded do
199202
^ha ->
200203
# elixir raises here undefined_bittype
201204
# we omit the spec

lib/elixir_sense/core/compiler/clauses.ex

+4-1
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,10 @@ defmodule ElixirSense.Core.Compiler.Clauses do
613613
# rescue expr() => rescue expanded_expr()
614614
defp expand_rescue({_, meta, _} = arg, s, e) do
615615
# TODO how to check for cursor here?
616-
case Compiler.Macro.expand_once(arg, %{e | line: Utils.get_line(meta)}) do
616+
expanded = Compiler.Macro.expand_once(arg, %{e | line: Utils.get_line(meta)})
617+
s = Compiler.collect_traces(s)
618+
619+
case expanded do
617620
^arg ->
618621
# elixir rejects this case
619622
# try to recover from error by generating fake expression

lib/elixir_sense/core/compiler/macro.ex

+2-2
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@ defmodule ElixirSense.Core.Compiler.Macro do
8585
defp do_expand_once({:__aliases__, meta, [head | tail] = list} = alias, env) do
8686
case NormalizedMacroEnv.expand_alias(env, meta, list, trace: false) do
8787
{:alias, alias} ->
88-
# TODO track alias
88+
:elixir_env.trace({:alias_reference, meta, alias}, env)
8989
{alias, true}
9090

9191
:error ->
9292
{head, _} = do_expand_once(head, env)
9393

9494
if is_atom(head) do
9595
receiver = Module.concat([head | tail])
96-
# TODO track alias
96+
:elixir_env.trace({:alias_reference, meta, receiver}, env)
9797
{receiver, true}
9898
else
9999
{alias, false}

lib/elixir_sense/core/compiler/typespec.ex

+4-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,10 @@ defmodule ElixirSense.Core.Compiler.Typespec do
327327
end
328328

329329
defp typespec({:%, struct_meta, [name, {:%{}, meta, fields}]}, vars, caller, state) do
330-
case Compiler.Macro.expand(name, %{caller | function: {:__info__, 1}}) do
330+
expanded = Compiler.Macro.expand(name, %{caller | function: {:__info__, 1}})
331+
state = Compiler.collect_traces(state)
332+
333+
case expanded do
331334
module when is_atom(module) ->
332335
# TODO register alias/struct
333336
struct =

0 commit comments

Comments
 (0)