Skip to content

Commit 4d93205

Browse files
committed
suggest record fields from compiled records on 1.18+
1 parent 4c4473b commit 4d93205

File tree

2 files changed

+140
-3
lines changed

2 files changed

+140
-3
lines changed

apps/language_server/lib/language_server/providers/completion/reducers/record.ex

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Record do
77
alias ElixirSense.Core.State
88
alias ElixirSense.Core.State.{RecordInfo, TypeInfo}
99
alias ElixirLS.Utils.Matcher
10+
alias ElixirSense.Core.Normalized.Code, as: NormalizedCode
1011

1112
@type field :: %{
1213
type: :field,
@@ -95,8 +96,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Record do
9596
cursor_position,
9697
not elixir_prefix
9798
),
98-
%RecordInfo{} = info <- records[{mod, fun}] do
99-
fields = get_fields(hint, mod, fun, info.fields, options_so_far, metadata_types)
99+
fields_info when is_list(fields_info) <- get_record_info(mod, fun, records) do
100+
fields = get_fields(hint, mod, fun, fields_info, options_so_far, metadata_types)
100101

101102
{fields, if(npar == 0 and cursor_at_option in [false, :maybe], do: :maybe_record_update)}
102103
else
@@ -105,6 +106,27 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Record do
105106
end
106107
end
107108

109+
defp get_record_info(mod, fun, records) do
110+
case records[{mod, fun}] do
111+
%RecordInfo{} = info ->
112+
info.fields
113+
114+
nil ->
115+
if Version.match?(System.version(), ">= 1.18.0-dev") do
116+
case NormalizedCode.get_docs(mod, :docs) do
117+
nil ->
118+
nil
119+
120+
docs ->
121+
Enum.find_value(docs, fn
122+
{{^fun, 1}, _, :macro, _, _, %{record: {_tag, fields}}} -> fields
123+
_ -> nil
124+
end)
125+
end
126+
end
127+
end
128+
end
129+
108130
defp get_fields(hint, module, record_name, fields, fields_so_far, types) do
109131
field_types = get_field_types(types, module, record_name)
110132

@@ -154,7 +176,47 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Record do
154176
when kind in [:type, :typep, :opaque] <- ast do
155177
field_types
156178
else
157-
_ -> []
179+
_ ->
180+
candidates =
181+
if Version.match?(System.version(), ">= 1.18.0-dev") do
182+
ElixirSense.Core.TypeInfo.find_all(module, fn info ->
183+
info.name in [record, :"#{record}_t", :t] and info.arity == 0
184+
end)
185+
else
186+
[]
187+
end
188+
189+
with [info | _] <- candidates,
190+
{:ok, ast} <- Code.string_to_quoted(info.spec),
191+
{:@, _,
192+
[
193+
{kind, _,
194+
[
195+
{:"::", _,
196+
[
197+
{_name, _, []},
198+
{:{}, _,
199+
[
200+
_tag
201+
| field_types
202+
]}
203+
]}
204+
]}
205+
]}
206+
when kind in [:type, :typep, :opaque] <- ast do
207+
field_types
208+
|> Enum.map(fn
209+
{:"::", _, [{name, _, context}, type]} when is_atom(name) and is_atom(context) ->
210+
{name, type}
211+
212+
_ ->
213+
nil
214+
end)
215+
|> Enum.reject(&is_nil/1)
216+
else
217+
_ ->
218+
[]
219+
end
158220
end
159221
end
160222
end

apps/language_server/test/providers/completion/suggestions_test.exs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4972,6 +4972,81 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do
49724972
] = suggestions |> Enum.filter(&(&1.name == "user"))
49734973
end
49744974

4975+
test "records fields" do
4976+
buffer = """
4977+
defmodule SomeSchema do
4978+
require ElixirSenseExample.ModuleWithRecord, as: R
4979+
4980+
def d() do
4981+
w = R.user()
4982+
w = R.user(n)
4983+
R.user(w, n)
4984+
R.user(w, name: "1", a)
4985+
end
4986+
end
4987+
"""
4988+
4989+
suggestions = Suggestion.suggestions(buffer, 5, 16)
4990+
4991+
assert [
4992+
%{
4993+
name: "age",
4994+
origin: "ElixirSenseExample.ModuleWithRecord.user",
4995+
type: :field,
4996+
call?: false,
4997+
subtype: :record_field,
4998+
type_spec: "integer()"
4999+
},
5000+
%{
5001+
name: "name",
5002+
origin: "ElixirSenseExample.ModuleWithRecord.user",
5003+
type: :field,
5004+
call?: false,
5005+
subtype: :record_field,
5006+
type_spec: "String.t()"
5007+
}
5008+
] = suggestions |> Enum.filter(&(&1.type == :field))
5009+
5010+
suggestions = Suggestion.suggestions(buffer, 6, 17)
5011+
5012+
assert [
5013+
%{
5014+
name: "name",
5015+
origin: "ElixirSenseExample.ModuleWithRecord.user",
5016+
type: :field,
5017+
call?: false,
5018+
subtype: :record_field,
5019+
type_spec: "String.t()"
5020+
}
5021+
] = suggestions |> Enum.filter(&(&1.type == :field))
5022+
5023+
suggestions = Suggestion.suggestions(buffer, 7, 16)
5024+
5025+
assert [
5026+
%{
5027+
name: "name",
5028+
origin: "ElixirSenseExample.ModuleWithRecord.user",
5029+
type: :field,
5030+
call?: false,
5031+
subtype: :record_field,
5032+
type_spec: "String.t()"
5033+
}
5034+
] = suggestions |> Enum.filter(&(&1.type == :field))
5035+
5036+
suggestions = Suggestion.suggestions(buffer, 8, 27)
5037+
5038+
assert [
5039+
%{
5040+
name: "age",
5041+
origin: "ElixirSenseExample.ModuleWithRecord.user",
5042+
type: :field,
5043+
call?: false,
5044+
subtype: :record_field,
5045+
type_spec: "integer()"
5046+
}
5047+
] = suggestions |> Enum.filter(&(&1.type == :field))
5048+
end
5049+
49755050
test "records from metadata fields" do
49765051
buffer = """
49775052
defmodule SomeSchema do

0 commit comments

Comments
 (0)