mt-lang 0.3.8 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. checksums.yaml +4 -4
  2. data/lib/milk_tea/base.rb +1 -2
  3. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +0 -2
  4. data/lib/milk_tea/bindings/bindgen/declaration.rb +0 -2
  5. data/lib/milk_tea/bindings/bindgen/emitter.rb +0 -2
  6. data/lib/milk_tea/bindings/bindgen/overrides.rb +0 -2
  7. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +51 -53
  8. data/lib/milk_tea/bindings/imported_bindings/generator.rb +7 -9
  9. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +0 -2
  10. data/lib/milk_tea/bindings/imported_bindings/naming.rb +6 -8
  11. data/lib/milk_tea/bindings/imported_bindings.rb +0 -2
  12. data/lib/milk_tea/bindings/opengl_registry.rb +3 -7
  13. data/lib/milk_tea/bindings/steamworks.rb +12 -16
  14. data/lib/milk_tea/bindings/upstream_sources.rb +10 -14
  15. data/lib/milk_tea/bindings/vendored_box2d.rb +10 -12
  16. data/lib/milk_tea/bindings/vendored_cjson.rb +6 -8
  17. data/lib/milk_tea/bindings/vendored_flecs.rb +10 -12
  18. data/lib/milk_tea/bindings/vendored_glfw.rb +11 -13
  19. data/lib/milk_tea/bindings/vendored_libuv.rb +11 -13
  20. data/lib/milk_tea/bindings/vendored_pcre2.rb +11 -13
  21. data/lib/milk_tea/bindings/vendored_raylib.rb +6 -10
  22. data/lib/milk_tea/bindings/vendored_sdl3.rb +11 -13
  23. data/lib/milk_tea/bindings/vendored_steamworks.rb +1 -5
  24. data/lib/milk_tea/core/ast.rb +103 -45
  25. data/lib/milk_tea/core/async_runtime_installer.rb +0 -2
  26. data/lib/milk_tea/core/bindings/attribute_binding.rb +1 -3
  27. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +147 -84
  28. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +394 -396
  29. data/lib/milk_tea/core/c_backend/expressions.rb +489 -476
  30. data/lib/milk_tea/core/c_backend/feature_detection.rb +446 -437
  31. data/lib/milk_tea/core/c_backend/format_helpers.rb +0 -2
  32. data/lib/milk_tea/core/c_backend/reachability.rb +339 -341
  33. data/lib/milk_tea/core/c_backend/reinterpret.rb +231 -217
  34. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +531 -526
  35. data/lib/milk_tea/core/c_backend/statements.rb +528 -530
  36. data/lib/milk_tea/core/c_backend/type_collectors.rb +1344 -1346
  37. data/lib/milk_tea/core/c_backend/type_declaration.rb +215 -213
  38. data/lib/milk_tea/core/c_backend/type_system.rb +347 -349
  39. data/lib/milk_tea/core/c_backend.rb +17 -9
  40. data/lib/milk_tea/core/compatibility_helpers.rb +0 -2
  41. data/lib/milk_tea/core/compile_time/const_eval.rb +7 -12
  42. data/lib/milk_tea/core/compile_time.rb +32 -38
  43. data/lib/milk_tea/core/control_flow/builder.rb +3 -5
  44. data/lib/milk_tea/core/control_flow/constant_propagation.rb +14 -17
  45. data/lib/milk_tea/core/control_flow/definite_assignment.rb +0 -1
  46. data/lib/milk_tea/core/ir.rb +10 -10
  47. data/lib/milk_tea/core/lexer/character_classes.rb +5 -7
  48. data/lib/milk_tea/core/lexer/format_strings.rb +7 -9
  49. data/lib/milk_tea/core/lexer/heredocs.rb +22 -22
  50. data/lib/milk_tea/core/lexer/indentation.rb +0 -2
  51. data/lib/milk_tea/core/lexer/numbers.rb +0 -2
  52. data/lib/milk_tea/core/lexer/recovery.rb +0 -2
  53. data/lib/milk_tea/core/lexer/strings.rb +0 -2
  54. data/lib/milk_tea/core/lexer/symbols.rb +0 -2
  55. data/lib/milk_tea/core/lexer/trivia.rb +0 -2
  56. data/lib/milk_tea/core/lexer.rb +0 -2
  57. data/lib/milk_tea/core/lowering/async/analysis.rb +0 -2
  58. data/lib/milk_tea/core/lowering/async/lowering.rb +1264 -1265
  59. data/lib/milk_tea/core/lowering/async/normalization.rb +0 -2
  60. data/lib/milk_tea/core/lowering/async.rb +627 -630
  61. data/lib/milk_tea/core/lowering/block.rb +413 -416
  62. data/lib/milk_tea/core/lowering/calls.rb +1381 -1384
  63. data/lib/milk_tea/core/lowering/declarations.rb +180 -183
  64. data/lib/milk_tea/core/lowering/dyn.rb +48 -50
  65. data/lib/milk_tea/core/lowering/events.rb +983 -985
  66. data/lib/milk_tea/core/lowering/expressions.rb +1513 -1518
  67. data/lib/milk_tea/core/lowering/foreign_cstr.rb +181 -184
  68. data/lib/milk_tea/core/lowering/functions.rb +185 -188
  69. data/lib/milk_tea/core/lowering/loops.rb +1028 -1031
  70. data/lib/milk_tea/core/lowering/proc.rb +363 -366
  71. data/lib/milk_tea/core/lowering/resolve.rb +2133 -2135
  72. data/lib/milk_tea/core/lowering/scans.rb +179 -181
  73. data/lib/milk_tea/core/lowering/str_buffer.rb +102 -105
  74. data/lib/milk_tea/core/lowering/utils.rb +1040 -1044
  75. data/lib/milk_tea/core/lowering.rb +5 -7
  76. data/lib/milk_tea/core/module_binder.rb +0 -2
  77. data/lib/milk_tea/core/module_loader/errors.rb +4 -2
  78. data/lib/milk_tea/core/module_loader.rb +113 -20
  79. data/lib/milk_tea/core/module_path_resolver.rb +0 -2
  80. data/lib/milk_tea/core/module_roots.rb +6 -8
  81. data/lib/milk_tea/core/parser/attributes.rb +0 -2
  82. data/lib/milk_tea/core/parser/blocks.rb +0 -2
  83. data/lib/milk_tea/core/parser/declarations.rb +0 -2
  84. data/lib/milk_tea/core/parser/expressions.rb +31 -14
  85. data/lib/milk_tea/core/parser/recovery.rb +0 -2
  86. data/lib/milk_tea/core/parser/statements.rb +0 -2
  87. data/lib/milk_tea/core/parser/types.rb +8 -10
  88. data/lib/milk_tea/core/parser.rb +1 -3
  89. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +31 -33
  90. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +0 -2
  91. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +0 -2
  92. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +0 -2
  93. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +14 -16
  94. data/lib/milk_tea/core/semantic_analyzer/calls.rb +0 -2
  95. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1 -14
  96. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +18 -21
  97. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +0 -3
  98. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +12 -15
  99. data/lib/milk_tea/core/semantic_analyzer/generics.rb +57 -59
  100. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +0 -2
  101. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +5 -9
  102. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +0 -3
  103. data/lib/milk_tea/core/semantic_analyzer/statements.rb +35 -40
  104. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +10 -12
  105. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +0 -6
  106. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +102 -105
  107. data/lib/milk_tea/core/semantic_analyzer.rb +17 -8
  108. data/lib/milk_tea/core/types/layout.rb +21 -22
  109. data/lib/milk_tea/core/types/predicates.rb +0 -2
  110. data/lib/milk_tea/core/types/registry.rb +16 -18
  111. data/lib/milk_tea/core/types/visitor.rb +0 -4
  112. data/lib/milk_tea/dap/backends/lldb_dap.rb +0 -2
  113. data/lib/milk_tea/dap/protocol.rb +1 -1
  114. data/lib/milk_tea/dap/server/breakpoints.rb +0 -2
  115. data/lib/milk_tea/dap/server/debug_map.rb +0 -2
  116. data/lib/milk_tea/dap/server/handlers.rb +0 -2
  117. data/lib/milk_tea/dap/server/launch.rb +0 -2
  118. data/lib/milk_tea/dap/server/lldb_backend.rb +8 -10
  119. data/lib/milk_tea/dap/server/pause_diagnostics.rb +14 -16
  120. data/lib/milk_tea/dap/server/utilities.rb +0 -2
  121. data/lib/milk_tea/dap/server/wire.rb +0 -2
  122. data/lib/milk_tea/dap/server.rb +0 -2
  123. data/lib/milk_tea/lsp/diagnostics.rb +16 -16
  124. data/lib/milk_tea/lsp/server/call_hierarchy.rb +11 -12
  125. data/lib/milk_tea/lsp/server/code_actions.rb +355 -357
  126. data/lib/milk_tea/lsp/server/code_lens.rb +23 -25
  127. data/lib/milk_tea/lsp/server/completion.rb +873 -875
  128. data/lib/milk_tea/lsp/server/configuration.rb +129 -131
  129. data/lib/milk_tea/lsp/server/debug_info.rb +0 -2
  130. data/lib/milk_tea/lsp/server/definition.rb +564 -565
  131. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +191 -193
  132. data/lib/milk_tea/lsp/server/execute_command.rb +0 -2
  133. data/lib/milk_tea/lsp/server/folding_range.rb +0 -4
  134. data/lib/milk_tea/lsp/server/formatting.rb +478 -480
  135. data/lib/milk_tea/lsp/server/hover.rb +1282 -1284
  136. data/lib/milk_tea/lsp/server/inlay_hints.rb +179 -181
  137. data/lib/milk_tea/lsp/server/lifecycle.rb +192 -194
  138. data/lib/milk_tea/lsp/server/linked_editing_range.rb +0 -2
  139. data/lib/milk_tea/lsp/server/on_type_formatting.rb +0 -2
  140. data/lib/milk_tea/lsp/server/progress.rb +0 -2
  141. data/lib/milk_tea/lsp/server/references.rb +316 -318
  142. data/lib/milk_tea/lsp/server/rename.rb +445 -447
  143. data/lib/milk_tea/lsp/server/selection_range.rb +0 -4
  144. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1318 -1320
  145. data/lib/milk_tea/lsp/server/signature_help.rb +146 -148
  146. data/lib/milk_tea/lsp/server/text_documents.rb +99 -101
  147. data/lib/milk_tea/lsp/server/type_hierarchy.rb +2 -4
  148. data/lib/milk_tea/lsp/server/utilities.rb +397 -399
  149. data/lib/milk_tea/lsp/server.rb +0 -3
  150. data/lib/milk_tea/lsp/workspace/analysis.rb +40 -42
  151. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +4 -10
  152. data/lib/milk_tea/lsp/workspace/utilities.rb +19 -19
  153. data/lib/milk_tea/packages/atomic_write.rb +4 -5
  154. data/lib/milk_tea/packages/dependency_solver.rb +15 -15
  155. data/lib/milk_tea/packages/lock.rb +4 -4
  156. data/lib/milk_tea/packages/manager_cli.rb +1 -1
  157. data/lib/milk_tea/packages/manifest.rb +3 -3
  158. data/lib/milk_tea/packages/registry_store.rb +4 -6
  159. data/lib/milk_tea/packages/services.rb +16 -16
  160. data/lib/milk_tea/packages/source_cache.rb +4 -6
  161. data/lib/milk_tea/packages/source_resolver.rb +44 -44
  162. data/lib/milk_tea/packages/version.rb +8 -8
  163. data/lib/milk_tea/tooling/build.rb +3 -7
  164. data/lib/milk_tea/tooling/build_cache.rb +0 -2
  165. data/lib/milk_tea/tooling/debug_info_formatter.rb +31 -33
  166. data/lib/milk_tea/tooling/debug_map.rb +8 -10
  167. data/lib/milk_tea/tooling/docs_app.rb +0 -2
  168. data/lib/milk_tea/tooling/linter/doc_tags.rb +25 -27
  169. data/lib/milk_tea/tooling/linter/fix_engine.rb +87 -24
  170. data/lib/milk_tea/tooling/linter/flow_rules.rb +44 -46
  171. data/lib/milk_tea/tooling/linter/imports_platform.rb +14 -16
  172. data/lib/milk_tea/tooling/linter/release_rules.rb +3 -5
  173. data/lib/milk_tea/tooling/linter/reserved_names.rb +23 -25
  174. data/lib/milk_tea/tooling/linter/rules.rb +87 -89
  175. data/lib/milk_tea/tooling/linter/source_helpers.rb +40 -42
  176. data/lib/milk_tea/tooling/linter/trailing_comma.rb +17 -19
  177. data/lib/milk_tea/tooling/linter/visitors.rb +162 -50
  178. data/lib/milk_tea/tooling/linter.rb +0 -2
  179. data/lib/milk_tea/tooling/project_scaffold.rb +0 -2
  180. data/lib/milk_tea/tooling/run.rb +0 -2
  181. data/lib/milk_tea/tooling/toolchain_cli.rb +0 -2
  182. data/std/cli.mt +13 -32
  183. data/std/color.mt +28 -28
  184. data/std/encoding.mt +4 -4
  185. data/std/graph.mt +1 -2
  186. data/std/hash.mt +14 -14
  187. data/std/http/server.mt +3 -3
  188. data/std/input.mt +1 -1
  189. data/std/json.mt +1 -1
  190. data/std/linear_algebra.mt +6 -6
  191. data/std/net/mux.mt +1 -1
  192. data/std/noise.mt +23 -23
  193. data/std/parse.mt +2 -2
  194. data/std/process.mt +3 -4
  195. data/std/sparse_set.mt +1 -1
  196. data/std/steering.mt +5 -5
  197. data/std/str.mt +1 -1
  198. data/std/tween.mt +4 -4
  199. data/std/utility.mt +3 -3
  200. metadata +2 -2
@@ -18,8 +18,6 @@ module MilkTea
18
18
  ptr ref span array dyn Option Result Task SoA str_buffer const_ptr simd
19
19
  ].freeze
20
20
 
21
- private
22
-
23
21
  def completion_data(name)
24
22
  { uri: @current_completion_uri || '', name: }
25
23
  end
@@ -39,1082 +37,1082 @@ module MilkTea
39
37
  result.empty? ? nil : result
40
38
  end
41
39
 
42
- def handle_completion(params)
43
- stages = new_perf_stages
44
- total_start = stages ? monotonic_time : nil
45
- uri = params['textDocument']['uri']
46
- @current_completion_uri = uri
47
- lsp_line = params['position']['line']
48
- lsp_char = params['position']['character']
49
- branch = 'none'
50
- item_count = 0
51
-
52
- prefix = measure_perf_stage(stages, 'prefix') { current_word_prefix(uri, lsp_line, lsp_char) }
53
-
54
- import_items = measure_perf_stage(stages, 'import_context') { import_completions(uri, lsp_line, lsp_char) }
55
- if import_items
56
- branch = 'import'
57
- item_count = import_items.length
58
- return { isIncomplete: false, items: import_items }
59
- end
60
-
61
- facts = measure_perf_stage(stages, 'facts') { @workspace.get_facts(uri) }
62
-
63
- unless facts
64
- branch = 'no-facts'
65
- return { isIncomplete: false, items: [] }
66
- end
40
+ def handle_completion(params)
41
+ stages = new_perf_stages
42
+ total_start = stages ? monotonic_time : nil
43
+ uri = params['textDocument']['uri']
44
+ @current_completion_uri = uri
45
+ lsp_line = params['position']['line']
46
+ lsp_char = params['position']['character']
47
+ branch = 'none'
48
+ item_count = 0
49
+
50
+ prefix = measure_perf_stage(stages, 'prefix') { current_word_prefix(uri, lsp_line, lsp_char) }
51
+
52
+ import_items = measure_perf_stage(stages, 'import_context') { import_completions(uri, lsp_line, lsp_char) }
53
+ if import_items
54
+ branch = 'import'
55
+ item_count = import_items.length
56
+ return { isIncomplete: false, items: import_items }
57
+ end
67
58
 
68
- # Attribute context: complete inside @[...]
69
- attr_items = attribute_completions(facts, uri, lsp_line, lsp_char)
70
- if attr_items
71
- branch = 'attribute'
72
- item_count = attr_items.length
73
- return { isIncomplete: false, items: attr_items }
74
- end
59
+ facts = measure_perf_stage(stages, 'facts') { @workspace.get_facts(uri) }
75
60
 
76
- # Format string interpolation: complete inside f"... #{ }
77
- fmt_items = format_string_completions(facts, uri, lsp_line, lsp_char)
78
- if fmt_items
79
- branch = 'format-string'
80
- item_count = fmt_items.length
81
- return { isIncomplete: false, items: fmt_items }
82
- end
61
+ unless facts
62
+ branch = 'no-facts'
63
+ return { isIncomplete: false, items: [] }
64
+ end
83
65
 
84
- # Named argument completions: inside function/struct call e.g. Point(x: 1, |)
85
- named_items = named_argument_completions(facts, uri, lsp_line, lsp_char)
86
- if named_items
87
- branch = 'named-arg'
88
- item_count = named_items.length
89
- return { isIncomplete: false, items: named_items }
90
- end
66
+ # Attribute context: complete inside @[...]
67
+ attr_items = attribute_completions(facts, uri, lsp_line, lsp_char)
68
+ if attr_items
69
+ branch = 'attribute'
70
+ item_count = attr_items.length
71
+ return { isIncomplete: false, items: attr_items }
72
+ end
91
73
 
92
- # Specialization context: inside name[...]
93
- spec_items = specialization_completions(facts, uri, lsp_line, lsp_char)
94
- if spec_items
95
- branch = 'specialization'
96
- item_count = spec_items.length
97
- return { isIncomplete: false, items: spec_items }
98
- end
74
+ # Format string interpolation: complete inside f"... #{ }
75
+ fmt_items = format_string_completions(facts, uri, lsp_line, lsp_char)
76
+ if fmt_items
77
+ branch = 'format-string'
78
+ item_count = fmt_items.length
79
+ return { isIncomplete: false, items: fmt_items }
80
+ end
99
81
 
100
- # When user is typing after '.', return module members or method completions.
101
- dot_recv = nil
102
- dot_recv_path = nil
103
- measure_perf_stage(stages, 'receiver_context') do
104
- dot_recv = @workspace.find_dot_receiver(uri, lsp_line, lsp_char)
105
- dot_recv_path = @workspace.find_dot_receiver_path(uri, lsp_line, lsp_char)
106
- end
107
- if dot_recv
108
- # Module member access: rl.init_window, rl.RAYWHITE, etc.
109
- if (module_binding = facts.imports[dot_recv])
110
- branch = 'module'
111
- items = measure_perf_stage(stages, 'build') do
112
- result = []
113
- module_binding.functions.each do |fname, binding|
114
- next unless prefix.empty? || fname.start_with?(prefix)
115
- params_str = format_params(binding.type.params)
116
- item = {
117
- label: fname,
118
- kind: 12, # Function
119
- detail: "function #{fname}(#{params_str}) -> #{binding.type.return_type}",
120
- insertText: fname,
121
- sortText: "0_#{fname}",
122
- data: completion_data(fname),
123
- }
124
- if (ld = label_details_for_params(params_str, module_binding.name))
125
- item[:labelDetails] = ld
126
- end
127
- if (snippet = snippet_for_callable(fname, binding.type.params))
128
- item[:insertText] = snippet
129
- item[:insertTextFormat] = 2
130
- end
131
- result << item
132
- end
133
- module_binding.values.each do |vname, binding|
134
- next unless prefix.empty? || vname.start_with?(prefix)
135
- result << {
136
- label: vname,
137
- kind: 13, # Variable
138
- detail: "#{vname}: #{binding.type}",
139
- insertText: vname,
140
- sortText: "1_#{vname}",
141
- data: completion_data(vname),
142
- }
143
- end
144
- module_binding.types.each do |tname, _type|
145
- next unless prefix.empty? || tname.start_with?(prefix)
146
- result << {
147
- label: tname,
148
- kind: 5, # Class
149
- detail: "type #{tname}",
150
- insertText: tname,
151
- sortText: "2_#{tname}",
152
- data: completion_data(tname),
153
- }
154
- end
155
- result
156
- end
157
- item_count = items.length
158
- return { isIncomplete: false, items: items }
82
+ # Named argument completions: inside function/struct call e.g. Point(x: 1, |)
83
+ named_items = named_argument_completions(facts, uri, lsp_line, lsp_char)
84
+ if named_items
85
+ branch = 'named-arg'
86
+ item_count = named_items.length
87
+ return { isIncomplete: false, items: named_items }
159
88
  end
160
- if (type_receiver = measure_perf_stage(stages, 'type_receiver') { resolve_type_receiver_info(facts, dot_recv, dot_recv_path) })
161
- receiver_label = type_receiver[:label]
162
- type = type_receiver[:type]
163
89
 
90
+ # Specialization context: inside name[...]
91
+ spec_items = specialization_completions(facts, uri, lsp_line, lsp_char)
92
+ if spec_items
93
+ branch = 'specialization'
94
+ item_count = spec_items.length
95
+ return { isIncomplete: false, items: spec_items }
96
+ end
164
97
 
165
- # Enum/Flags member access: Color.RED, KeyboardKey.A, etc.
166
- if type.is_a?(Types::EnumBase)
167
- branch = 'enum-members'
98
+ # When user is typing after '.', return module members or method completions.
99
+ dot_recv = nil
100
+ dot_recv_path = nil
101
+ measure_perf_stage(stages, 'receiver_context') do
102
+ dot_recv = @workspace.find_dot_receiver(uri, lsp_line, lsp_char)
103
+ dot_recv_path = @workspace.find_dot_receiver_path(uri, lsp_line, lsp_char)
104
+ end
105
+ if dot_recv
106
+ # Module member access: rl.init_window, rl.RAYWHITE, etc.
107
+ if (module_binding = facts.imports[dot_recv])
108
+ branch = 'module'
168
109
  items = measure_perf_stage(stages, 'build') do
169
- type.members.filter_map do |mname|
170
- next if !prefix.empty? && !mname.start_with?(prefix)
171
- {
172
- label: mname,
173
- kind: 22, # EnumMember
174
- detail: "#{receiver_label}.#{mname}",
175
- insertText: mname,
176
- sortText: "0_#{mname}",
177
- data: completion_data(mname),
110
+ result = []
111
+ module_binding.functions.each do |fname, binding|
112
+ next unless prefix.empty? || fname.start_with?(prefix)
113
+ params_str = format_params(binding.type.params)
114
+ item = {
115
+ label: fname,
116
+ kind: 12, # Function
117
+ detail: "function #{fname}(#{params_str}) -> #{binding.type.return_type}",
118
+ insertText: fname,
119
+ sortText: "0_#{fname}",
120
+ data: completion_data(fname),
121
+ }
122
+ if (ld = label_details_for_params(params_str, module_binding.name))
123
+ item[:labelDetails] = ld
124
+ end
125
+ if (snippet = snippet_for_callable(fname, binding.type.params))
126
+ item[:insertText] = snippet
127
+ item[:insertTextFormat] = 2
128
+ end
129
+ result << item
130
+ end
131
+ module_binding.values.each do |vname, binding|
132
+ next unless prefix.empty? || vname.start_with?(prefix)
133
+ result << {
134
+ label: vname,
135
+ kind: 13, # Variable
136
+ detail: "#{vname}: #{binding.type}",
137
+ insertText: vname,
138
+ sortText: "1_#{vname}",
139
+ data: completion_data(vname),
140
+ }
141
+ end
142
+ module_binding.types.each do |tname, _type|
143
+ next unless prefix.empty? || tname.start_with?(prefix)
144
+ result << {
145
+ label: tname,
146
+ kind: 5, # Class
147
+ detail: "type #{tname}",
148
+ insertText: tname,
149
+ sortText: "2_#{tname}",
150
+ data: completion_data(tname),
178
151
  }
179
152
  end
153
+ result
180
154
  end
181
155
  item_count = items.length
182
156
  return { isIncomplete: false, items: items }
183
157
  end
158
+ if (type_receiver = measure_perf_stage(stages, 'type_receiver') { resolve_type_receiver_info(facts, dot_recv, dot_recv_path) })
159
+ receiver_label = type_receiver[:label]
160
+ type = type_receiver[:type]
161
+
162
+
163
+ # Enum/Flags member access: Color.RED, KeyboardKey.A, etc.
164
+ if type.is_a?(Types::EnumBase)
165
+ branch = 'enum-members'
166
+ items = measure_perf_stage(stages, 'build') do
167
+ type.members.filter_map do |mname|
168
+ next if !prefix.empty? && !mname.start_with?(prefix)
169
+ {
170
+ label: mname,
171
+ kind: 22, # EnumMember
172
+ detail: "#{receiver_label}.#{mname}",
173
+ insertText: mname,
174
+ sortText: "0_#{mname}",
175
+ data: completion_data(mname),
176
+ }
177
+ end
178
+ end
179
+ item_count = items.length
180
+ return { isIncomplete: false, items: items }
181
+ end
184
182
 
185
- # Variant arm access: Option.none, Result.success, etc.
186
- if type.is_a?(Types::Variant)
187
- branch = 'variant-arms'
188
- items = measure_perf_stage(stages, 'build') do
189
- type.arm_names.filter_map do |aname|
190
- next if !prefix.empty? && !aname.start_with?(prefix)
191
- {
192
- label: aname,
193
- kind: 22, # EnumMember
194
- detail: "#{receiver_label}.#{aname}",
195
- insertText: aname,
196
- sortText: "0_#{aname}",
197
- data: completion_data(aname),
198
- }
183
+ # Variant arm access: Option.none, Result.success, etc.
184
+ if type.is_a?(Types::Variant)
185
+ branch = 'variant-arms'
186
+ items = measure_perf_stage(stages, 'build') do
187
+ type.arm_names.filter_map do |aname|
188
+ next if !prefix.empty? && !aname.start_with?(prefix)
189
+ {
190
+ label: aname,
191
+ kind: 22, # EnumMember
192
+ detail: "#{receiver_label}.#{aname}",
193
+ insertText: aname,
194
+ sortText: "0_#{aname}",
195
+ data: completion_data(aname),
196
+ }
197
+ end
199
198
  end
199
+ item_count = items.length
200
+ return { isIncomplete: false, items: items }
200
201
  end
201
- item_count = items.length
202
- return { isIncomplete: false, items: items }
203
- end
204
202
 
205
- # Nested struct type members: ShapeGroup.CircleData, etc.
206
- if type.is_a?(Types::Struct) && type.respond_to?(:nested_types) && type.nested_types.any?
207
- branch = 'nested-types'
208
- items = measure_perf_stage(stages, 'build') do
209
- type.nested_types.filter_map do |nt_name, _nt_type|
210
- next if !prefix.empty? && !nt_name.start_with?(prefix)
211
- {
212
- label: nt_name,
213
- kind: 5, # Class/type
214
- detail: "#{receiver_label}.#{nt_name}",
215
- insertText: nt_name,
216
- sortText: "1_#{nt_name}",
217
- data: completion_data(nt_name),
218
- }
203
+ # Nested struct type members: ShapeGroup.CircleData, etc.
204
+ if type.is_a?(Types::Struct) && type.respond_to?(:nested_types) && type.nested_types.any?
205
+ branch = 'nested-types'
206
+ items = measure_perf_stage(stages, 'build') do
207
+ type.nested_types.filter_map do |nt_name, _nt_type|
208
+ next if !prefix.empty? && !nt_name.start_with?(prefix)
209
+ {
210
+ label: nt_name,
211
+ kind: 5, # Class/type
212
+ detail: "#{receiver_label}.#{nt_name}",
213
+ insertText: nt_name,
214
+ sortText: "1_#{nt_name}",
215
+ data: completion_data(nt_name),
216
+ }
217
+ end
219
218
  end
219
+ item_count = items.length
220
+ return { isIncomplete: false, items: items } unless items.empty?
220
221
  end
221
- item_count = items.length
222
- return { isIncomplete: false, items: items } unless items.empty?
223
- end
224
222
 
225
- items = measure_perf_stage(stages, 'build') { completion_items_for_type_receiver(facts, type, prefix) }
226
- unless items.empty?
227
- branch = 'type-receiver'
228
- item_count = items.length
229
- return { isIncomplete: false, items: items }
223
+ items = measure_perf_stage(stages, 'build') { completion_items_for_type_receiver(facts, type, prefix) }
224
+ unless items.empty?
225
+ branch = 'type-receiver'
226
+ item_count = items.length
227
+ return { isIncomplete: false, items: items }
228
+ end
230
229
  end
231
- end
232
230
 
233
- if dot_recv_path && dot_recv_path.include?('.')
234
- segments = dot_recv_path.split('.', 2)
235
- mod_alias = segments.first
236
- value_name = segments[1]
237
- if mod_alias && value_name && (mod_binding = facts.imports[mod_alias])
238
- if (val_binding = mod_binding.values[value_name])
239
- receiver_type = measure_perf_stage(stages, 'imported_value_receiver') { val_binding.type }
240
- items = measure_perf_stage(stages, 'build') { completion_items_for_value_receiver(facts, receiver_type, prefix) }
241
- unless items.empty?
242
- branch = 'imported-value-receiver'
243
- item_count = items.length
244
- return { isIncomplete: false, items: items }
231
+ if dot_recv_path && dot_recv_path.include?('.')
232
+ segments = dot_recv_path.split('.', 2)
233
+ mod_alias = segments.first
234
+ value_name = segments[1]
235
+ if mod_alias && value_name && (mod_binding = facts.imports[mod_alias])
236
+ if (val_binding = mod_binding.values[value_name])
237
+ receiver_type = measure_perf_stage(stages, 'imported_value_receiver') { val_binding.type }
238
+ items = measure_perf_stage(stages, 'build') { completion_items_for_value_receiver(facts, receiver_type, prefix) }
239
+ unless items.empty?
240
+ branch = 'imported-value-receiver'
241
+ item_count = items.length
242
+ return { isIncomplete: false, items: items }
243
+ end
244
+ end
245
+ else
246
+ chain_items = measure_perf_stage(stages, 'value_chain') { value_chain_completions(facts, dot_recv_path, lsp_line, lsp_char, prefix) }
247
+ if chain_items
248
+ branch = 'value-chain'
249
+ item_count = chain_items.length
250
+ return { isIncomplete: false, items: chain_items }
245
251
  end
246
- end
247
- else
248
- chain_items = measure_perf_stage(stages, 'value_chain') { value_chain_completions(facts, dot_recv_path, lsp_line, lsp_char, prefix) }
249
- if chain_items
250
- branch = 'value-chain'
251
- item_count = chain_items.length
252
- return { isIncomplete: false, items: chain_items }
253
252
  end
254
253
  end
255
- end
256
254
 
257
- if (receiver_type = measure_perf_stage(stages, 'value_receiver') { resolve_dot_receiver_value_type(facts, dot_recv, lsp_line + 1, lsp_char + 1) })
258
- items = measure_perf_stage(stages, 'build') { completion_items_for_value_receiver(facts, receiver_type, prefix) }
259
- unless items.empty?
260
- branch = 'value-receiver'
261
- item_count = items.length
262
- return { isIncomplete: false, items: items }
255
+ if (receiver_type = measure_perf_stage(stages, 'value_receiver') { resolve_dot_receiver_value_type(facts, dot_recv, lsp_line + 1, lsp_char + 1) })
256
+ items = measure_perf_stage(stages, 'build') { completion_items_for_value_receiver(facts, receiver_type, prefix) }
257
+ unless items.empty?
258
+ branch = 'value-receiver'
259
+ item_count = items.length
260
+ return { isIncomplete: false, items: items }
261
+ end
263
262
  end
264
- end
265
263
 
266
- # Method completions on a non-module receiver.
267
- branch = 'method-fallback'
268
- method_items = measure_perf_stage(stages, 'build') do
269
- result = []
270
- facts.methods.each do |_recv_type, methods|
271
- methods.each do |mname, binding|
272
- next unless prefix.empty? || mname.start_with?(prefix)
273
-
274
- params_str = format_params(binding.type.params)
275
- item = {
276
- label: mname,
277
- kind: 6, # Method
278
- detail: "#{mname}(#{params_str}) -> #{binding.type.return_type}",
279
- insertText: mname,
280
- sortText: "0_#{mname}",
281
- data: completion_data(mname),
282
- }
283
- if (ld = label_details_for_params(params_str, nil))
284
- item[:labelDetails] = ld
285
- end
286
- if (snippet = snippet_for_callable(mname, binding.type.params))
287
- item[:insertText] = snippet
288
- item[:insertTextFormat] = 2
264
+ # Method completions on a non-module receiver.
265
+ branch = 'method-fallback'
266
+ method_items = measure_perf_stage(stages, 'build') do
267
+ result = []
268
+ facts.methods.each do |_recv_type, methods|
269
+ methods.each do |mname, binding|
270
+ next unless prefix.empty? || mname.start_with?(prefix)
271
+
272
+ params_str = format_params(binding.type.params)
273
+ item = {
274
+ label: mname,
275
+ kind: 6, # Method
276
+ detail: "#{mname}(#{params_str}) -> #{binding.type.return_type}",
277
+ insertText: mname,
278
+ sortText: "0_#{mname}",
279
+ data: completion_data(mname),
280
+ }
281
+ if (ld = label_details_for_params(params_str, nil))
282
+ item[:labelDetails] = ld
283
+ end
284
+ if (snippet = snippet_for_callable(mname, binding.type.params))
285
+ item[:insertText] = snippet
286
+ item[:insertTextFormat] = 2
287
+ end
288
+ result << item
289
289
  end
290
- result << item
291
290
  end
291
+ result
292
292
  end
293
- result
294
- end
295
- item_count = method_items.length
296
- truncated = item_count > MAX_COMPLETION_ITEMS
297
- if truncated
298
- method_items = method_items.first(MAX_COMPLETION_ITEMS)
299
- item_count = MAX_COMPLETION_ITEMS
293
+ item_count = method_items.length
294
+ truncated = item_count > MAX_COMPLETION_ITEMS
295
+ if truncated
296
+ method_items = method_items.first(MAX_COMPLETION_ITEMS)
297
+ item_count = MAX_COMPLETION_ITEMS
298
+ end
299
+ return { isIncomplete: truncated, items: method_items }
300
300
  end
301
- return { isIncomplete: truncated, items: method_items }
302
- end
303
301
 
304
- branch = 'global'
305
- items = measure_perf_stage(stages, 'build') do
306
- result = []
307
- function_docs_cache = {}
302
+ branch = 'global'
303
+ items = measure_perf_stage(stages, 'build') do
304
+ result = []
305
+ function_docs_cache = {}
308
306
 
309
- # Functions
310
- facts.functions.each do |name, binding|
311
- next unless prefix.empty? || name.start_with?(prefix)
307
+ # Functions
308
+ facts.functions.each do |name, binding|
309
+ next unless prefix.empty? || name.start_with?(prefix)
312
310
 
313
- params_str = format_params(binding.type.params)
314
- entry = {
315
- label: name,
316
- kind: 12, # Function
317
- detail: "function #{name}(#{params_str}) -> #{binding.type.return_type}",
318
- insertText: name,
319
- sortText: "0_#{name}",
320
- data: completion_data(name),
321
- }
311
+ params_str = format_params(binding.type.params)
312
+ entry = {
313
+ label: name,
314
+ kind: 12, # Function
315
+ detail: "function #{name}(#{params_str}) -> #{binding.type.return_type}",
316
+ insertText: name,
317
+ sortText: "0_#{name}",
318
+ data: completion_data(name),
319
+ }
322
320
 
323
- if (ld = label_details_for_params(params_str, nil))
324
- entry[:labelDetails] = ld
325
- end
321
+ if (ld = label_details_for_params(params_str, nil))
322
+ entry[:labelDetails] = ld
323
+ end
324
+
325
+ if (snippet = snippet_for_callable(name, binding.type.params))
326
+ entry[:insertText] = snippet
327
+ entry[:insertTextFormat] = 2
328
+ end
329
+
330
+ docs = completion_function_documentation(uri, name, cache: function_docs_cache)
331
+ unless docs.empty?
332
+ entry[:documentation] = {
333
+ kind: 'markdown',
334
+ value: docs,
335
+ }
336
+ end
326
337
 
327
- if (snippet = snippet_for_callable(name, binding.type.params))
328
- entry[:insertText] = snippet
329
- entry[:insertTextFormat] = 2
338
+ result << entry
330
339
  end
331
340
 
332
- docs = completion_function_documentation(uri, name, cache: function_docs_cache)
333
- unless docs.empty?
334
- entry[:documentation] = {
335
- kind: 'markdown',
336
- value: docs,
341
+ # Types
342
+ builtin_names = SemanticAnalyzer::INSTALLABLE_BUILTIN_TYPE_NAMES
343
+ facts.types.each do |name, type|
344
+ next if builtin_names.include?(name)
345
+ next unless prefix.empty? || name.start_with?(prefix)
346
+
347
+ kind = case type
348
+ when Types::StructInstance then 22 # Struct
349
+ when Types::EnumBase, Types::Variant then 13 # Enum
350
+ else 7 # Class/type
351
+ end
352
+
353
+ result << {
354
+ label: name,
355
+ kind: kind,
356
+ detail: "type #{name}",
357
+ insertText: name,
358
+ sortText: "1_#{name}",
359
+ data: completion_data(name),
337
360
  }
338
361
  end
339
362
 
340
- result << entry
341
- end
363
+ # Imported modules
364
+ facts.imports.each do |name, module_binding|
365
+ next unless prefix.empty? || name.start_with?(prefix)
342
366
 
343
- # Types
344
- builtin_names = SemanticAnalyzer::INSTALLABLE_BUILTIN_TYPE_NAMES
345
- facts.types.each do |name, type|
346
- next if builtin_names.include?(name)
347
- next unless prefix.empty? || name.start_with?(prefix)
367
+ result << {
368
+ label: name,
369
+ kind: 2, # Module
370
+ detail: "module #{module_binding.name}",
371
+ insertText: name,
372
+ sortText: "2_#{name}",
373
+ data: completion_data(name),
374
+ }
375
+ end
348
376
 
349
- kind = case type
350
- when Types::StructInstance then 22 # Struct
351
- when Types::EnumBase, Types::Variant then 13 # Enum
352
- else 7 # Class/type
353
- end
354
-
355
- result << {
356
- label: name,
357
- kind: kind,
358
- detail: "type #{name}",
359
- insertText: name,
360
- sortText: "1_#{name}",
361
- data: completion_data(name),
362
- }
363
- end
377
+ # Values
378
+ facts.values.each do |name, binding|
379
+ next unless prefix.empty? || name.start_with?(prefix)
364
380
 
365
- # Imported modules
366
- facts.imports.each do |name, module_binding|
367
- next unless prefix.empty? || name.start_with?(prefix)
381
+ result << {
382
+ label: name,
383
+ kind: 13, # Variable
384
+ detail: "#{name}: #{binding.type}",
385
+ insertText: name,
386
+ sortText: "3_#{name}",
387
+ data: completion_data(name),
388
+ }
389
+ end
368
390
 
369
- result << {
370
- label: name,
371
- kind: 2, # Module
372
- detail: "module #{module_binding.name}",
373
- insertText: name,
374
- sortText: "2_#{name}",
375
- data: completion_data(name),
376
- }
377
- end
391
+ # Locals (variables and parameters visible in current scope)
392
+ frame = enclosing_completion_frame(facts, lsp_line + 1)
393
+ if frame
394
+ snapshot = latest_completion_snapshot(frame, lsp_line + 1, lsp_char + 1)
395
+ if snapshot
396
+ snapshot.bindings.each do |name, binding|
397
+ next unless prefix.empty? || name.start_with?(prefix)
398
+ next unless binding.respond_to?(:name) && binding.respond_to?(:type)
399
+
400
+ detail_label = case binding.respond_to?(:kind) && binding.kind
401
+ when :param then "parameter #{name}: #{binding.type}"
402
+ when :local then "local #{name}: #{binding.type}"
403
+ else "#{name}: #{binding.type}"
404
+ end
405
+
406
+ result << {
407
+ label: name,
408
+ kind: 13, # Variable
409
+ detail: detail_label,
410
+ insertText: name,
411
+ sortText: "3_#{name}",
412
+ data: completion_data(name),
413
+ }
414
+ end
415
+ end
416
+ end
378
417
 
379
- # Values
380
- facts.values.each do |name, binding|
381
- next unless prefix.empty? || name.start_with?(prefix)
418
+ TYPE_CONSTRUCTOR_KEYWORDS.each do |tc|
419
+ next unless prefix.empty? || tc.start_with?(prefix)
382
420
 
383
- result << {
384
- label: name,
385
- kind: 13, # Variable
386
- detail: "#{name}: #{binding.type}",
387
- insertText: name,
388
- sortText: "3_#{name}",
389
- data: completion_data(name),
390
- }
391
- end
421
+ result << {
422
+ label: tc,
423
+ kind: 14, # Keyword
424
+ detail: "type constructor #{tc}",
425
+ insertText: tc,
426
+ sortText: "8_#{tc}",
427
+ data: completion_data(tc),
428
+ }
429
+ end
392
430
 
393
- # Locals (variables and parameters visible in current scope)
394
- frame = enclosing_completion_frame(facts, lsp_line + 1)
395
- if frame
396
- snapshot = latest_completion_snapshot(frame, lsp_line + 1, lsp_char + 1)
397
- if snapshot
398
- snapshot.bindings.each do |name, binding|
399
- next unless prefix.empty? || name.start_with?(prefix)
400
- next unless binding.respond_to?(:name) && binding.respond_to?(:type)
431
+ COMPLETION_KEYWORDS.each do |kw|
432
+ next unless prefix.empty? || kw.start_with?(prefix)
401
433
 
402
- detail_label = case binding.respond_to?(:kind) && binding.kind
403
- when :param then "parameter #{name}: #{binding.type}"
404
- when :local then "local #{name}: #{binding.type}"
405
- else "#{name}: #{binding.type}"
406
- end
407
-
408
- result << {
409
- label: name,
410
- kind: 13, # Variable
411
- detail: detail_label,
412
- insertText: name,
413
- sortText: "3_#{name}",
414
- data: completion_data(name),
415
- }
416
- end
434
+ result << {
435
+ label: kw,
436
+ kind: 14, # Keyword
437
+ detail: "keyword #{kw}",
438
+ insertText: kw,
439
+ sortText: "9_#{kw}",
440
+ data: completion_data(kw),
441
+ }
417
442
  end
418
- end
419
-
420
- TYPE_CONSTRUCTOR_KEYWORDS.each do |tc|
421
- next unless prefix.empty? || tc.start_with?(prefix)
422
443
 
423
- result << {
424
- label: tc,
425
- kind: 14, # Keyword
426
- detail: "type constructor #{tc}",
427
- insertText: tc,
428
- sortText: "8_#{tc}",
429
- data: completion_data(tc),
430
- }
444
+ result
431
445
  end
432
446
 
433
- COMPLETION_KEYWORDS.each do |kw|
434
- next unless prefix.empty? || kw.start_with?(prefix)
435
-
436
- result << {
437
- label: kw,
438
- kind: 14, # Keyword
439
- detail: "keyword #{kw}",
440
- insertText: kw,
441
- sortText: "9_#{kw}",
442
- data: completion_data(kw),
443
- }
447
+ item_count = items.length
448
+ truncated = item_count > MAX_COMPLETION_ITEMS
449
+ if truncated
450
+ items = items.first(MAX_COMPLETION_ITEMS)
451
+ item_count = MAX_COMPLETION_ITEMS
444
452
  end
445
-
446
- result
447
- end
448
-
449
- item_count = items.length
450
- truncated = item_count > MAX_COMPLETION_ITEMS
451
- if truncated
452
- items = items.first(MAX_COMPLETION_ITEMS)
453
- item_count = MAX_COMPLETION_ITEMS
453
+ { isIncomplete: truncated, items: items }
454
+ rescue StandardError => e
455
+ branch = 'error'
456
+ warn "Error in completion handler: #{e.message}"
457
+ { isIncomplete: false, items: [] }
458
+ ensure
459
+ log_request_stage_breakdown('textDocument/completion', total_start, uri: uri, stages: stages, summary: "branch=#{branch} items=#{item_count}")
454
460
  end
455
- { isIncomplete: truncated, items: items }
456
- rescue StandardError => e
457
- branch = 'error'
458
- warn "Error in completion handler: #{e.message}"
459
- { isIncomplete: false, items: [] }
460
- ensure
461
- log_request_stage_breakdown('textDocument/completion', total_start, uri: uri, stages: stages, summary: "branch=#{branch} items=#{item_count}")
462
- end
463
461
 
464
- def value_chain_completions(facts, dot_recv_path, lsp_line, lsp_char, prefix)
465
- return nil unless dot_recv_path&.include?('.')
462
+ def value_chain_completions(facts, dot_recv_path, lsp_line, lsp_char, prefix)
463
+ return nil unless dot_recv_path&.include?('.')
466
464
 
467
- chain_segments = dot_recv_path.split('.')
468
- return nil if chain_segments.length < 2
465
+ chain_segments = dot_recv_path.split('.')
466
+ return nil if chain_segments.length < 2
469
467
 
470
- first_seg = chain_segments.first
471
- first_type = resolve_dot_receiver_value_type(facts, first_seg, lsp_line + 1, lsp_char + 1)
472
- return nil unless first_type
468
+ first_seg = chain_segments.first
469
+ first_type = resolve_dot_receiver_value_type(facts, first_seg, lsp_line + 1, lsp_char + 1)
470
+ return nil unless first_type
473
471
 
474
- current_type = first_type
475
- chain_segments[1..].each do |seg|
476
- field_owner = project_field_receiver_type_for_completion(current_type, facts)
477
- if field_owner.respond_to?(:field) && (field_type = field_owner.field(seg))
478
- current_type = field_type
479
- else
480
- return nil
472
+ current_type = first_type
473
+ chain_segments[1..].each do |seg|
474
+ field_owner = project_field_receiver_type_for_completion(current_type, facts)
475
+ if field_owner.respond_to?(:field) && (field_type = field_owner.field(seg))
476
+ current_type = field_type
477
+ else
478
+ return nil
479
+ end
481
480
  end
481
+
482
+ items = completion_items_for_value_receiver(facts, current_type, prefix)
483
+ items.empty? ? nil : items
482
484
  end
483
485
 
484
- items = completion_items_for_value_receiver(facts, current_type, prefix)
485
- items.empty? ? nil : items
486
- end
486
+ def completion_items_for_type_receiver(facts, receiver_type, prefix)
487
+ methods_for_receiver_type(facts, receiver_type).filter_map do |mname, binding|
488
+ next unless binding.ast.is_a?(AST::MethodDef) && binding.ast.kind == :static
487
489
 
488
- def completion_items_for_type_receiver(facts, receiver_type, prefix)
489
- methods_for_receiver_type(facts, receiver_type).filter_map do |mname, binding|
490
- next unless binding.ast.is_a?(AST::MethodDef) && binding.ast.kind == :static
491
-
492
- display_name = mname.delete_prefix("static:")
493
- next unless prefix.empty? || display_name.start_with?(prefix)
494
-
495
- params_str = format_params(binding.type.params)
496
- item = {
497
- label: display_name,
498
- kind: 6, # Method
499
- detail: "#{display_name}(#{params_str}) -> #{binding.type.return_type}",
500
- insertText: display_name,
501
- sortText: "0_#{display_name}",
502
- data: completion_data(display_name),
503
- }
504
- if (ld = label_details_for_params(params_str, nil))
505
- item[:labelDetails] = ld
506
- end
507
- if (snippet = snippet_for_callable(display_name, binding.type.params))
508
- item[:insertText] = snippet
509
- item[:insertTextFormat] = 2
490
+ display_name = mname.delete_prefix("static:")
491
+ next unless prefix.empty? || display_name.start_with?(prefix)
492
+
493
+ params_str = format_params(binding.type.params)
494
+ item = {
495
+ label: display_name,
496
+ kind: 6, # Method
497
+ detail: "#{display_name}(#{params_str}) -> #{binding.type.return_type}",
498
+ insertText: display_name,
499
+ sortText: "0_#{display_name}",
500
+ data: completion_data(display_name),
501
+ }
502
+ if (ld = label_details_for_params(params_str, nil))
503
+ item[:labelDetails] = ld
504
+ end
505
+ if (snippet = snippet_for_callable(display_name, binding.type.params))
506
+ item[:insertText] = snippet
507
+ item[:insertTextFormat] = 2
508
+ end
509
+ item
510
510
  end
511
- item
512
511
  end
513
- end
514
512
 
515
- def resolve_nested_type_binding(facts, name)
516
- return { qualified_name: nil, type: nil } unless facts
513
+ def resolve_nested_type_binding(facts, name)
514
+ return { qualified_name: nil, type: nil } unless facts
517
515
 
518
- facts.types.each_value do |type|
519
- next unless type.respond_to?(:nested_types)
520
- if (nested = type.nested_types[name])
521
- return { qualified_name: "#{type.name}.#{name}", type: nested }
516
+ facts.types.each_value do |type|
517
+ next unless type.respond_to?(:nested_types)
518
+ if (nested = type.nested_types[name])
519
+ return { qualified_name: "#{type.name}.#{name}", type: nested }
520
+ end
522
521
  end
522
+ { qualified_name: nil, type: nil }
523
523
  end
524
- { qualified_name: nil, type: nil }
525
- end
526
524
 
527
- def resolve_type_receiver_info(facts, receiver_name, receiver_path)
528
- if facts.types.key?(receiver_name)
529
- type = facts.types.fetch(receiver_name)
530
- module_name = type.respond_to?(:module_name) ? type.module_name : facts.module_name
531
- return { label: receiver_name, type:, module_name: }
532
- end
525
+ def resolve_type_receiver_info(facts, receiver_name, receiver_path)
526
+ if facts.types.key?(receiver_name)
527
+ type = facts.types.fetch(receiver_name)
528
+ module_name = type.respond_to?(:module_name) ? type.module_name : facts.module_name
529
+ return { label: receiver_name, type:, module_name: }
530
+ end
533
531
 
534
- nested = resolve_nested_type_binding(facts, receiver_name)
535
- return { label: nested[:qualified_name], type: nested[:type], module_name: nested[:type].module_name } if nested[:type]
532
+ nested = resolve_nested_type_binding(facts, receiver_name)
533
+ return { label: nested[:qualified_name], type: nested[:type], module_name: nested[:type].module_name } if nested[:type]
536
534
 
537
- return nil unless receiver_path&.include?('.')
535
+ return nil unless receiver_path&.include?('.')
538
536
 
539
- module_alias, type_name = receiver_path.split('.', 2)
540
- return nil unless module_alias && type_name
537
+ module_alias, type_name = receiver_path.split('.', 2)
538
+ return nil unless module_alias && type_name
541
539
 
542
- module_binding = facts.imports[module_alias]
543
- return nil unless module_binding
540
+ module_binding = facts.imports[module_alias]
541
+ return nil unless module_binding
544
542
 
545
- type = module_binding.types[type_name]
546
- return nil unless type
543
+ type = module_binding.types[type_name]
544
+ return nil unless type
547
545
 
548
- { label: receiver_path, type:, module_name: module_binding.name }
549
- end
546
+ { label: receiver_path, type:, module_name: module_binding.name }
547
+ end
550
548
 
551
- def resolve_static_type_receiver_method(facts, receiver_name, receiver_path, method_name)
552
- receiver_info = resolve_type_receiver_info(facts, receiver_name, receiver_path)
553
- return nil unless receiver_info
549
+ def resolve_static_type_receiver_method(facts, receiver_name, receiver_path, method_name)
550
+ receiver_info = resolve_type_receiver_info(facts, receiver_name, receiver_path)
551
+ return nil unless receiver_info
554
552
 
555
- binding = static_method_binding_for_receiver(facts, receiver_info[:type], method_name)
556
- return nil unless binding
553
+ binding = static_method_binding_for_receiver(facts, receiver_info[:type], method_name)
554
+ return nil unless binding
557
555
 
558
- receiver_info.merge(binding:)
559
- end
556
+ receiver_info.merge(binding:)
557
+ end
560
558
 
561
- def static_method_binding_for_receiver(facts, receiver_type, method_name)
562
- methods = methods_for_receiver_type(facts, receiver_type)
563
- binding = methods[method_name] || methods["static:#{method_name}"]
564
- return nil unless binding&.ast.is_a?(AST::MethodDef) && binding.ast.kind == :static
559
+ def static_method_binding_for_receiver(facts, receiver_type, method_name)
560
+ methods = methods_for_receiver_type(facts, receiver_type)
561
+ binding = methods[method_name] || methods["static:#{method_name}"]
562
+ return nil unless binding&.ast.is_a?(AST::MethodDef) && binding.ast.kind == :static
565
563
 
566
- binding
567
- end
564
+ binding
565
+ end
568
566
 
569
- def resolve_static_type_reference_target(uri, token, facts)
570
- token_end_char = token.column - 1 + token.lexeme.length
571
- receiver_name = @workspace.find_dot_receiver(uri, token.line - 1, token_end_char)
572
- receiver_path = @workspace.find_dot_receiver_path(uri, token.line - 1, token_end_char)
567
+ def resolve_static_type_reference_target(uri, token, facts)
568
+ token_end_char = token.column - 1 + token.lexeme.length
569
+ receiver_name = @workspace.find_dot_receiver(uri, token.line - 1, token_end_char)
570
+ receiver_path = @workspace.find_dot_receiver_path(uri, token.line - 1, token_end_char)
573
571
 
574
- if (target = resolve_static_type_receiver_method(facts, receiver_name, receiver_path, token.lexeme))
575
- location = module_member_binding_location(uri, target[:module_name], token.lexeme, target[:binding])
576
- location ||= module_member_definition_location(uri, target[:module_name], token.lexeme)
577
- return target.merge(location:)
578
- end
572
+ if (target = resolve_static_type_receiver_method(facts, receiver_name, receiver_path, token.lexeme))
573
+ location = module_member_binding_location(uri, target[:module_name], token.lexeme, target[:binding])
574
+ location ||= module_member_definition_location(uri, target[:module_name], token.lexeme)
575
+ return target.merge(location:)
576
+ end
579
577
 
580
- binding = static_method_binding_at_token(facts, token)
581
- return nil unless binding
582
-
583
- location = module_member_binding_location(uri, facts.module_name, token.lexeme, binding)
584
- location ||= module_member_definition_location(uri, facts.module_name, token.lexeme)
585
- {
586
- label: token.lexeme,
587
- type: binding.declared_receiver_type,
588
- module_name: facts.module_name,
589
- binding:,
590
- location:,
591
- }
592
- end
578
+ binding = static_method_binding_at_token(facts, token)
579
+ return nil unless binding
580
+
581
+ location = module_member_binding_location(uri, facts.module_name, token.lexeme, binding)
582
+ location ||= module_member_definition_location(uri, facts.module_name, token.lexeme)
583
+ {
584
+ label: token.lexeme,
585
+ type: binding.declared_receiver_type,
586
+ module_name: facts.module_name,
587
+ binding:,
588
+ location:,
589
+ }
590
+ end
593
591
 
594
- def static_method_binding_at_token(facts, token)
595
- binding = method_binding_at_token(facts, token)
596
- return nil unless binding&.ast.is_a?(AST::MethodDef) && binding.ast.kind == :static
592
+ def static_method_binding_at_token(facts, token)
593
+ binding = method_binding_at_token(facts, token)
594
+ return nil unless binding&.ast.is_a?(AST::MethodDef) && binding.ast.kind == :static
597
595
 
598
- binding
599
- end
596
+ binding
597
+ end
600
598
 
601
- def static_type_method_references(target, include_declaration:)
602
- name = target[:binding].name
603
- target_uri = target[:location][:uri]
599
+ def static_type_method_references(target, include_declaration:)
600
+ name = target[:binding].name
601
+ target_uri = target[:location][:uri]
604
602
 
605
- candidate_uris = nil
606
- if target_uri
607
- target_facts = @workspace.get_facts(target_uri)
608
- if target_facts&.module_name
609
- importing = @workspace.reverse_import_dependents_for(target_facts.module_name)
610
- candidate_uris = importing.to_a + [target_uri] if importing && !importing.empty?
603
+ candidate_uris = nil
604
+ if target_uri
605
+ target_facts = @workspace.get_facts(target_uri)
606
+ if target_facts&.module_name
607
+ importing = @workspace.reverse_import_dependents_for(target_facts.module_name)
608
+ candidate_uris = importing.to_a + [target_uri] if importing && !importing.empty?
609
+ end
611
610
  end
612
- end
613
611
 
614
- refs = if candidate_uris
615
- @workspace.find_all_references_in(name, candidate_uris)
616
- else
617
- @workspace.find_all_references(name)
618
- end
619
- refs.filter do |ref|
620
- if location_matches_reference?(target[:location], ref)
621
- include_declaration
622
- else
623
- static_type_method_reference?(ref, target)
612
+ refs = if candidate_uris
613
+ @workspace.find_all_references_in(name, candidate_uris)
614
+ else
615
+ @workspace.find_all_references(name)
616
+ end
617
+ refs.filter do |ref|
618
+ if location_matches_reference?(target[:location], ref)
619
+ include_declaration
620
+ else
621
+ static_type_method_reference?(ref, target)
622
+ end
624
623
  end
625
624
  end
626
- end
627
625
 
628
- def static_type_method_reference?(ref, target)
629
- token = @workspace.find_token_at(ref[:uri], ref.dig(:range, :start, :line), ref.dig(:range, :start, :character))
630
- return false unless token&.type == :identifier
626
+ def static_type_method_reference?(ref, target)
627
+ token = @workspace.find_token_at(ref[:uri], ref.dig(:range, :start, :line), ref.dig(:range, :start, :character))
628
+ return false unless token&.type == :identifier
631
629
 
632
- facts = @workspace.get_facts(ref[:uri])
633
- return false unless facts
630
+ facts = @workspace.get_facts(ref[:uri])
631
+ return false unless facts
634
632
 
635
- token_end_char = ref.dig(:range, :end, :character)
636
- receiver_name = @workspace.find_dot_receiver(ref[:uri], ref.dig(:range, :start, :line), token_end_char)
637
- receiver_path = @workspace.find_dot_receiver_path(ref[:uri], ref.dig(:range, :start, :line), token_end_char)
638
- candidate = resolve_static_type_receiver_method(facts, receiver_name, receiver_path, token.lexeme)
639
- return false unless candidate
633
+ token_end_char = ref.dig(:range, :end, :character)
634
+ receiver_name = @workspace.find_dot_receiver(ref[:uri], ref.dig(:range, :start, :line), token_end_char)
635
+ receiver_path = @workspace.find_dot_receiver_path(ref[:uri], ref.dig(:range, :start, :line), token_end_char)
636
+ candidate = resolve_static_type_receiver_method(facts, receiver_name, receiver_path, token.lexeme)
637
+ return false unless candidate
640
638
 
641
- candidate_location = module_member_binding_location(ref[:uri], candidate[:module_name], token.lexeme, candidate[:binding])
642
- candidate_location ||= module_member_definition_location(ref[:uri], candidate[:module_name], token.lexeme)
643
- same_location?(candidate_location, target[:location])
644
- end
639
+ candidate_location = module_member_binding_location(ref[:uri], candidate[:module_name], token.lexeme, candidate[:binding])
640
+ candidate_location ||= module_member_definition_location(ref[:uri], candidate[:module_name], token.lexeme)
641
+ same_location?(candidate_location, target[:location])
642
+ end
645
643
 
646
- def location_matches_reference?(location, ref)
647
- return false unless location
644
+ def location_matches_reference?(location, ref)
645
+ return false unless location
648
646
 
649
- same_location?(location, {
650
- uri: ref[:uri],
651
- range: ref[:range]
652
- })
653
- end
647
+ same_location?(location, {
648
+ uri: ref[:uri],
649
+ range: ref[:range]
650
+ })
651
+ end
654
652
 
655
- def same_location?(left, right)
656
- return false unless left && right
653
+ def same_location?(left, right)
654
+ return false unless left && right
657
655
 
658
- left[:uri] == right[:uri] && left[:range] == right[:range]
659
- end
656
+ left[:uri] == right[:uri] && left[:range] == right[:range]
657
+ end
660
658
 
661
- def module_member_access_info(tokens, index)
662
- dot_index = previous_non_trivia_token_index(tokens, index)
663
- return nil unless dot_index && tokens[dot_index].type == :dot
659
+ def module_member_access_info(tokens, index)
660
+ dot_index = previous_non_trivia_token_index(tokens, index)
661
+ return nil unless dot_index && tokens[dot_index].type == :dot
664
662
 
665
- receiver_index = previous_non_trivia_token_index(tokens, dot_index)
666
- return nil unless receiver_index
663
+ receiver_index = previous_non_trivia_token_index(tokens, dot_index)
664
+ return nil unless receiver_index
667
665
 
668
- receiver = tokens[receiver_index]
669
- return nil unless receiver.type == :identifier
666
+ receiver = tokens[receiver_index]
667
+ return nil unless receiver.type == :identifier
670
668
 
671
- { receiver: receiver.lexeme }
672
- end
669
+ { receiver: receiver.lexeme }
670
+ end
673
671
 
674
- def completion_items_for_value_receiver(facts, receiver_type, prefix)
675
- items = []
672
+ def completion_items_for_value_receiver(facts, receiver_type, prefix)
673
+ items = []
676
674
 
677
- field_receiver_type = project_field_receiver_type_for_completion(receiver_type, facts)
678
- if field_receiver_type.respond_to?(:fields)
679
- field_receiver_type.fields.each do |fname, ftype|
680
- next unless prefix.empty? || fname.start_with?(prefix)
675
+ field_receiver_type = project_field_receiver_type_for_completion(receiver_type, facts)
676
+ if field_receiver_type.respond_to?(:fields)
677
+ field_receiver_type.fields.each do |fname, ftype|
678
+ next unless prefix.empty? || fname.start_with?(prefix)
681
679
 
682
- items << {
683
- label: fname,
684
- kind: 8, # Field
685
- detail: "#{fname}: #{ftype}",
686
- insertText: fname,
687
- sortText: "0_#{fname}",
688
- data: completion_data(fname),
689
- }
680
+ items << {
681
+ label: fname,
682
+ kind: 8, # Field
683
+ detail: "#{fname}: #{ftype}",
684
+ insertText: fname,
685
+ sortText: "0_#{fname}",
686
+ data: completion_data(fname),
687
+ }
688
+ end
690
689
  end
691
- end
692
690
 
693
- method_receiver_type = project_method_receiver_type_for_completion(receiver_type)
694
- methods = methods_for_receiver_type(facts, method_receiver_type)
695
- methods.each do |mname, binding|
696
- next unless prefix.empty? || mname.start_with?(prefix)
697
-
698
- params_str = format_params(binding.type.params)
699
- item = {
700
- label: mname,
701
- kind: 6, # Method
702
- detail: "#{mname}(#{params_str}) -> #{binding.type.return_type}",
703
- insertText: mname,
704
- sortText: "1_#{mname}",
705
- data: completion_data(mname),
706
- }
707
- if (ld = label_details_for_params(params_str, nil))
708
- item[:labelDetails] = ld
709
- end
710
- if (snippet = snippet_for_callable(mname, binding.type.params))
711
- item[:insertText] = snippet
712
- item[:insertTextFormat] = 2
691
+ method_receiver_type = project_method_receiver_type_for_completion(receiver_type)
692
+ methods = methods_for_receiver_type(facts, method_receiver_type)
693
+ methods.each do |mname, binding|
694
+ next unless prefix.empty? || mname.start_with?(prefix)
695
+
696
+ params_str = format_params(binding.type.params)
697
+ item = {
698
+ label: mname,
699
+ kind: 6, # Method
700
+ detail: "#{mname}(#{params_str}) -> #{binding.type.return_type}",
701
+ insertText: mname,
702
+ sortText: "1_#{mname}",
703
+ data: completion_data(mname),
704
+ }
705
+ if (ld = label_details_for_params(params_str, nil))
706
+ item[:labelDetails] = ld
707
+ end
708
+ if (snippet = snippet_for_callable(mname, binding.type.params))
709
+ item[:insertText] = snippet
710
+ item[:insertTextFormat] = 2
711
+ end
712
+ items << item
713
713
  end
714
- items << item
715
- end
716
714
 
717
- items
718
- end
715
+ items
716
+ end
719
717
 
720
- def methods_for_receiver_type(facts, receiver_type)
721
- methods = {}
722
- return methods unless receiver_type
718
+ def methods_for_receiver_type(facts, receiver_type)
719
+ methods = {}
720
+ return methods unless receiver_type
723
721
 
724
- receiver_candidates = [receiver_type]
725
- dispatch_receiver_type = method_dispatch_receiver_type_for_completion(receiver_type)
726
- receiver_candidates << dispatch_receiver_type if dispatch_receiver_type != receiver_type
722
+ receiver_candidates = [receiver_type]
723
+ dispatch_receiver_type = method_dispatch_receiver_type_for_completion(receiver_type)
724
+ receiver_candidates << dispatch_receiver_type if dispatch_receiver_type != receiver_type
727
725
 
728
- # For GenericInstance or Nullable-wrapped GenericInstance, also try the
729
- # struct definition from facts.types — method registration keys are
730
- # always the GenericStructDefinition, not a GenericInstance.
731
- [dispatch_receiver_type, receiver_type].each do |candidate|
732
- base = candidate
733
- base = base.base while base.is_a?(Types::Nullable)
734
- next unless base.is_a?(Types::GenericInstance)
726
+ # For GenericInstance or Nullable-wrapped GenericInstance, also try the
727
+ # struct definition from facts.types — method registration keys are
728
+ # always the GenericStructDefinition, not a GenericInstance.
729
+ [dispatch_receiver_type, receiver_type].each do |candidate|
730
+ base = candidate
731
+ base = base.base while base.is_a?(Types::Nullable)
732
+ next unless base.is_a?(Types::GenericInstance)
735
733
 
736
- definition = facts.types[base.name]
737
- receiver_candidates << definition if definition && !receiver_candidates.include?(definition)
738
- end
739
-
740
- receiver_candidates.each do |candidate|
741
- facts.methods.fetch(candidate, {}).each do |name, binding|
742
- methods[name] ||= binding
734
+ definition = facts.types[base.name]
735
+ receiver_candidates << definition if definition && !receiver_candidates.include?(definition)
743
736
  end
744
- end
745
737
 
746
- facts.imports.each_value do |module_binding|
747
738
  receiver_candidates.each do |candidate|
748
- module_binding.methods.fetch(candidate, {}).each do |name, binding|
739
+ facts.methods.fetch(candidate, {}).each do |name, binding|
749
740
  methods[name] ||= binding
750
741
  end
751
742
  end
752
- end
753
- methods
754
- end
755
-
756
- def method_dispatch_receiver_type_for_completion(receiver_type)
757
- return receiver_type.definition if receiver_type.is_a?(Types::StructInstance) || receiver_type.is_a?(Types::VariantInstance)
758
743
 
759
- if receiver_type.is_a?(Types::Nullable)
760
- return method_dispatch_receiver_type_for_completion(receiver_type.base)
744
+ facts.imports.each_value do |module_binding|
745
+ receiver_candidates.each do |candidate|
746
+ module_binding.methods.fetch(candidate, {}).each do |name, binding|
747
+ methods[name] ||= binding
748
+ end
749
+ end
750
+ end
751
+ methods
761
752
  end
762
753
 
763
- return receiver_type unless receiver_type.is_a?(Types::GenericInstance)
754
+ def method_dispatch_receiver_type_for_completion(receiver_type)
755
+ return receiver_type.definition if receiver_type.is_a?(Types::StructInstance) || receiver_type.is_a?(Types::VariantInstance)
764
756
 
765
- dispatch_receiver_type = Types::Registry.generic_instance(
766
- receiver_type.name,
767
- receiver_type.arguments.each_with_index.map do |argument, index|
768
- argument.is_a?(Types::LiteralTypeArg) ? argument : Types::TypeVar.new("__receiver_arg#{index}")
769
- end,
770
- )
771
-
772
- dispatch_receiver_type == receiver_type ? receiver_type : dispatch_receiver_type
773
- end
757
+ if receiver_type.is_a?(Types::Nullable)
758
+ return method_dispatch_receiver_type_for_completion(receiver_type.base)
759
+ end
774
760
 
775
- def project_field_receiver_type_for_completion(type, facts = nil)
776
- type = type.base while type.is_a?(Types::Nullable)
777
- type = project_receiver_type_for_completion(type)
778
- return type.definition if type.is_a?(Types::StructInstance) || type.is_a?(Types::VariantInstance)
779
- return field_type_from_struct_definition(type, facts) if facts
761
+ return receiver_type unless receiver_type.is_a?(Types::GenericInstance)
780
762
 
781
- type
782
- end
763
+ dispatch_receiver_type = Types::Registry.generic_instance(
764
+ receiver_type.name,
765
+ receiver_type.arguments.each_with_index.map do |argument, index|
766
+ argument.is_a?(Types::LiteralTypeArg) ? argument : Types::TypeVar.new("__receiver_arg#{index}")
767
+ end,
768
+ )
783
769
 
784
- def field_type_from_struct_definition(type, facts)
785
- return type unless type.is_a?(Types::GenericInstance)
786
- return type if ref_type_name?(type) || pointer_type_name?(type)
770
+ dispatch_receiver_type == receiver_type ? receiver_type : dispatch_receiver_type
771
+ end
787
772
 
788
- struct_def = facts.types[type.name]
789
- struct_def if struct_def&.respond_to?(:fields)
790
- end
773
+ def project_field_receiver_type_for_completion(type, facts = nil)
774
+ type = type.base while type.is_a?(Types::Nullable)
775
+ type = project_receiver_type_for_completion(type)
776
+ return type.definition if type.is_a?(Types::StructInstance) || type.is_a?(Types::VariantInstance)
777
+ return field_type_from_struct_definition(type, facts) if facts
791
778
 
792
- def project_method_receiver_type_for_completion(type)
793
- project_receiver_type_for_completion(type)
794
- end
779
+ type
780
+ end
795
781
 
796
- def project_receiver_type_for_completion(type)
797
- return type.arguments.first if ref_type_name?(type)
798
- return type.arguments.first if pointer_type_name?(type)
782
+ def field_type_from_struct_definition(type, facts)
783
+ return type unless type.is_a?(Types::GenericInstance)
784
+ return type if ref_type_name?(type) || pointer_type_name?(type)
799
785
 
800
- type
801
- end
786
+ struct_def = facts.types[type.name]
787
+ struct_def if struct_def&.respond_to?(:fields)
788
+ end
802
789
 
803
- def field_owner_type(receiver_type, facts = nil)
804
- aggregate_type = project_field_receiver_type_for_completion(receiver_type, facts)
805
- return aggregate_type.definition if aggregate_type.is_a?(Types::StructInstance) || aggregate_type.is_a?(Types::VariantInstance)
790
+ def project_method_receiver_type_for_completion(type)
791
+ project_receiver_type_for_completion(type)
792
+ end
806
793
 
807
- aggregate_type
808
- end
794
+ def project_receiver_type_for_completion(type)
795
+ return type.arguments.first if ref_type_name?(type)
796
+ return type.arguments.first if pointer_type_name?(type)
809
797
 
810
- def ref_type_name?(type)
811
- type.is_a?(Types::GenericInstance) && type.name == 'ref' && type.arguments.length == 1
812
- end
798
+ type
799
+ end
813
800
 
814
- def pointer_type_name?(type)
815
- type.is_a?(Types::GenericInstance) && %w[ptr const_ptr].include?(type.name) && type.arguments.length == 1
816
- end
801
+ def field_owner_type(receiver_type, facts = nil)
802
+ aggregate_type = project_field_receiver_type_for_completion(receiver_type, facts)
803
+ return aggregate_type.definition if aggregate_type.is_a?(Types::StructInstance) || aggregate_type.is_a?(Types::VariantInstance)
817
804
 
818
- def handle_completion_resolve(params)
819
- data = params['data']
820
- return params unless data.is_a?(Hash)
805
+ aggregate_type
806
+ end
821
807
 
822
- name = data['name']
823
- return params if name.to_s.empty?
808
+ def ref_type_name?(type)
809
+ type.is_a?(Types::GenericInstance) && type.name == 'ref' && type.arguments.length == 1
810
+ end
824
811
 
825
- uri = data['uri'] || ''
826
- definition_entry = @workspace.find_definition_token_global(name, preferred_uri: uri)
827
- return params unless definition_entry
812
+ def pointer_type_name?(type)
813
+ type.is_a?(Types::GenericInstance) && %w[ptr const_ptr].include?(type.name) && type.arguments.length == 1
814
+ end
828
815
 
829
- doc_comment = @workspace.doc_comment_data_for_definition(definition_entry[:uri], definition_entry[:token])
830
- docs = signature_help_markdown_for_doc_comment(doc_comment)
831
- return params if docs.empty?
816
+ def handle_completion_resolve(params)
817
+ data = params['data']
818
+ return params unless data.is_a?(Hash)
832
819
 
833
- params.merge('documentation' => { 'kind' => 'markdown', 'value' => docs })
834
- rescue StandardError => e
835
- warn "Error in completion resolve handler: #{e.message}"
836
- params
837
- end
820
+ name = data['name']
821
+ return params if name.to_s.empty?
838
822
 
839
- def import_completions(uri, lsp_line, lsp_char)
840
- content = @workspace.get_content(uri)
841
- lines = content.split("\n", -1)
842
- line = lines[lsp_line] || ''
843
- stripped = line.lstrip
844
- return nil unless stripped.start_with?('import ')
823
+ uri = data['uri'] || ''
824
+ definition_entry = @workspace.find_definition_token_global(name, preferred_uri: uri)
825
+ return params unless definition_entry
845
826
 
846
- import_kw_pos = line.index('import ')
847
- after_import = line[(import_kw_pos + 7)..].to_s
827
+ doc_comment = @workspace.doc_comment_data_for_definition(definition_entry[:uri], definition_entry[:token])
828
+ docs = signature_help_markdown_for_doc_comment(doc_comment)
829
+ return params if docs.empty?
848
830
 
849
- if (as_match = after_import.match(/\s+as\s+/))
850
- alias_pos = as_match.begin(0)
851
- cursor_in_import = lsp_char - import_kw_pos - 7
852
- return nil if cursor_in_import > alias_pos
853
- after_import = after_import[0...alias_pos]
831
+ params.merge('documentation' => { 'kind' => 'markdown', 'value' => docs })
832
+ rescue StandardError => e
833
+ warn "Error in completion resolve handler: #{e.message}"
834
+ params
854
835
  end
855
836
 
856
- cursor_in_import = lsp_char - import_kw_pos - 7
857
- cursor_in_import = [[cursor_in_import, after_import.length].min, 0].max
837
+ def import_completions(uri, lsp_line, lsp_char)
838
+ content = @workspace.get_content(uri)
839
+ lines = content.split("\n", -1)
840
+ line = lines[lsp_line] || ''
841
+ stripped = line.lstrip
842
+ return nil unless stripped.start_with?('import ')
843
+
844
+ import_kw_pos = line.index('import ')
845
+ after_import = line[(import_kw_pos + 7)..].to_s
846
+
847
+ if (as_match = after_import.match(/\s+as\s+/))
848
+ alias_pos = as_match.begin(0)
849
+ cursor_in_import = lsp_char - import_kw_pos - 7
850
+ return nil if cursor_in_import > alias_pos
851
+ after_import = after_import[0...alias_pos]
852
+ end
858
853
 
859
- path_typed = after_import[0...cursor_in_import].strip
854
+ cursor_in_import = lsp_char - import_kw_pos - 7
855
+ cursor_in_import = [[cursor_in_import, after_import.length].min, 0].max
860
856
 
861
- segments = path_typed.split('.', -1)
862
- dir_segments = segments[0...-1]
863
- filter = segments.last.to_s
857
+ path_typed = after_import[0...cursor_in_import].strip
864
858
 
865
- current_path = uri_to_path(uri)
866
- return nil unless current_path
859
+ segments = path_typed.split('.', -1)
860
+ dir_segments = segments[0...-1]
861
+ filter = segments.last.to_s
867
862
 
868
- roots = MilkTea::ModuleRoots.roots_for_path(current_path)
869
- fs_dir = dir_segments.join(File::SEPARATOR)
863
+ current_path = uri_to_path(uri)
864
+ return nil unless current_path
870
865
 
871
- modules = {}
872
- filter_lower = filter.downcase
866
+ roots = MilkTea::ModuleRoots.roots_for_path(current_path)
867
+ fs_dir = dir_segments.join(File::SEPARATOR)
873
868
 
874
- roots.each do |root|
875
- search_dir = fs_dir.empty? ? root : File.join(root, fs_dir)
876
- next unless File.directory?(search_dir)
869
+ modules = {}
870
+ filter_lower = filter.downcase
877
871
 
878
- Dir.children(search_dir).sort.each do |name|
879
- next if name.start_with?('.')
880
- full_path = File.join(search_dir, name)
872
+ roots.each do |root|
873
+ search_dir = fs_dir.empty? ? root : File.join(root, fs_dir)
874
+ next unless File.directory?(search_dir)
881
875
 
882
- if name.end_with?('.mt')
883
- mod_name = name.delete_suffix('.mt')
884
- next if mod_name.start_with?('.')
885
- next if full_path == current_path
886
- next unless filter.empty? || mod_name.downcase.start_with?(filter_lower)
887
- modules[mod_name] = mod_name
888
- elsif File.directory?(full_path) && module_dir_contains_mt?(full_path)
889
- next unless filter.empty? || name.downcase.start_with?(filter_lower)
890
- modules[name] = name
876
+ Dir.children(search_dir).sort.each do |name|
877
+ next if name.start_with?('.')
878
+ full_path = File.join(search_dir, name)
879
+
880
+ if name.end_with?('.mt')
881
+ mod_name = name.delete_suffix('.mt')
882
+ next if mod_name.start_with?('.')
883
+ next if full_path == current_path
884
+ next unless filter.empty? || mod_name.downcase.start_with?(filter_lower)
885
+ modules[mod_name] = mod_name
886
+ elsif File.directory?(full_path) && module_dir_contains_mt?(full_path)
887
+ next unless filter.empty? || name.downcase.start_with?(filter_lower)
888
+ modules[name] = name
889
+ end
891
890
  end
892
891
  end
893
- end
894
892
 
895
- return nil if modules.empty?
893
+ return nil if modules.empty?
896
894
 
897
- modules.values.sort.map do |mod_name|
898
- {
899
- label: mod_name,
900
- kind: 2,
901
- detail: "module #{mod_name}",
902
- insertText: mod_name,
903
- sortText: "0_#{mod_name}",
904
- data: completion_data(mod_name),
905
- }
895
+ modules.values.sort.map do |mod_name|
896
+ {
897
+ label: mod_name,
898
+ kind: 2,
899
+ detail: "module #{mod_name}",
900
+ insertText: mod_name,
901
+ sortText: "0_#{mod_name}",
902
+ data: completion_data(mod_name),
903
+ }
904
+ end
906
905
  end
907
- end
908
906
 
909
- def module_dir_contains_mt?(dir)
910
- Dir.children(dir).any? do |name|
911
- next false if name.start_with?('.')
912
- full = File.join(dir, name)
913
- name.end_with?('.mt') || (File.directory?(full) && module_dir_contains_mt?(full))
907
+ def module_dir_contains_mt?(dir)
908
+ Dir.children(dir).any? do |name|
909
+ next false if name.start_with?('.')
910
+ full = File.join(dir, name)
911
+ name.end_with?('.mt') || (File.directory?(full) && module_dir_contains_mt?(full))
912
+ end
914
913
  end
915
- end
916
914
 
917
- def attribute_completions(facts, uri, line, char)
918
- content = @workspace.get_content(uri)
919
- return nil unless content
920
- lines = content.split("\n", -1)
921
- line_text = lines[line] || ''
922
- return nil if line_text.empty?
923
-
924
- attr_match = line_text[0...char].match(/@\[(\w*)$/)
925
- return nil unless attr_match
926
-
927
- prefix = attr_match[1]
928
- items = []
929
-
930
- %w[packed align deprecated].each do |name|
931
- next unless prefix.empty? || name.start_with?(prefix)
932
- items << {
933
- label: name,
934
- kind: 14,
935
- detail: "built-in attribute #{name}",
936
- insertText: name,
937
- sortText: "0_#{name}",
938
- data: completion_data(name),
939
- }
940
- end
915
+ def attribute_completions(facts, uri, line, char)
916
+ content = @workspace.get_content(uri)
917
+ return nil unless content
918
+ lines = content.split("\n", -1)
919
+ line_text = lines[line] || ''
920
+ return nil if line_text.empty?
921
+
922
+ attr_match = line_text[0...char].match(/@\[(\w*)$/)
923
+ return nil unless attr_match
924
+
925
+ prefix = attr_match[1]
926
+ items = []
941
927
 
942
- if facts
943
- facts.attributes&.each do |name, _binding|
928
+ %w[packed align deprecated].each do |name|
944
929
  next unless prefix.empty? || name.start_with?(prefix)
945
930
  items << {
946
931
  label: name,
947
932
  kind: 14,
948
- detail: "attribute #{name}",
933
+ detail: "built-in attribute #{name}",
949
934
  insertText: name,
950
- sortText: "1_#{name}",
935
+ sortText: "0_#{name}",
951
936
  data: completion_data(name),
952
937
  }
953
938
  end
954
- end
955
-
956
- items.empty? ? nil : items
957
- end
958
-
959
- def format_string_completions(facts, uri, line, char)
960
- return nil unless facts
961
-
962
- content = @workspace.get_content(uri)
963
- return nil unless content
964
- lines = content.split("\n", -1)
965
- line_text = lines[line] || ''
966
- return nil if line_text.empty?
967
-
968
- text_before = line_text[0...char]
969
- open_pos = text_before.rindex('#{')
970
- return nil unless open_pos
971
-
972
- close_pos = text_before.index('}', open_pos)
973
- return nil if close_pos && close_pos < char
974
-
975
- prefix = text_before[(open_pos + 2)..] || ''
976
- items = []
977
-
978
- facts.values.each do |name, binding|
979
- next unless prefix.empty? || name.start_with?(prefix)
980
- items << {
981
- label: name,
982
- kind: 13,
983
- detail: "#{name}: #{binding.type}",
984
- insertText: name,
985
- sortText: "0_#{name}",
986
- data: completion_data(name),
987
- }
988
- end
989
-
990
- facts.functions.each do |name, _binding|
991
- next unless prefix.empty? || name.start_with?(prefix)
992
- items << {
993
- label: name,
994
- kind: 12,
995
- detail: "function #{name}",
996
- insertText: name,
997
- sortText: "1_#{name}",
998
- data: completion_data(name),
999
- }
1000
- end
1001
939
 
1002
- frame = enclosing_completion_frame(facts, line + 1)
1003
- if frame
1004
- snapshot = latest_completion_snapshot(frame, line + 1, char + 1)
1005
- if snapshot
1006
- snapshot.bindings.each do |name, binding|
940
+ if facts
941
+ facts.attributes&.each do |name, _binding|
1007
942
  next unless prefix.empty? || name.start_with?(prefix)
1008
- next unless binding.respond_to?(:name) && binding.respond_to?(:type)
1009
943
  items << {
1010
944
  label: name,
1011
- kind: 13,
1012
- detail: "#{name}: #{binding.type}",
945
+ kind: 14,
946
+ detail: "attribute #{name}",
1013
947
  insertText: name,
1014
- sortText: "2_#{name}",
948
+ sortText: "1_#{name}",
1015
949
  data: completion_data(name),
1016
950
  }
1017
951
  end
1018
952
  end
1019
- end
1020
-
1021
- items.empty? ? nil : items
1022
- end
1023
953
 
1024
- def named_argument_completions(facts, uri, line, char)
1025
- return nil unless facts
1026
-
1027
- content = @workspace.get_content(uri)
1028
- return nil unless content
1029
- lines = content.split("\n", -1)
1030
- line_text = lines[line] || ''
1031
- return nil if line_text.empty?
954
+ items.empty? ? nil : items
955
+ end
1032
956
 
1033
- text_before = line_text[0...char]
957
+ def format_string_completions(facts, uri, line, char)
958
+ return nil unless facts
1034
959
 
1035
- call_match = text_before.match(/([\w.]+)\s*\(\s*(?:[^)]*,\s*)\s*$/)
1036
- call_match ||= text_before.match(/([\w.]+)\s*\(\s*$/)
960
+ content = @workspace.get_content(uri)
961
+ return nil unless content
962
+ lines = content.split("\n", -1)
963
+ line_text = lines[line] || ''
964
+ return nil if line_text.empty?
1037
965
 
1038
- return nil unless call_match
966
+ text_before = line_text[0...char]
967
+ open_pos = text_before.rindex('#{')
968
+ return nil unless open_pos
1039
969
 
1040
- callable_name = call_match[1]
1041
- prefix_match = text_before.match(/(?:^|[,\s(])(\w*)$/)
1042
- prefix = prefix_match ? prefix_match[1] : ''
970
+ close_pos = text_before.index('}', open_pos)
971
+ return nil if close_pos && close_pos < char
1043
972
 
1044
- already_provided = text_before.scan(/(\w+)\s*[=:]/).flatten.to_set
973
+ prefix = text_before[(open_pos + 2)..] || ''
974
+ items = []
1045
975
 
1046
- items = []
976
+ facts.values.each do |name, binding|
977
+ next unless prefix.empty? || name.start_with?(prefix)
978
+ items << {
979
+ label: name,
980
+ kind: 13,
981
+ detail: "#{name}: #{binding.type}",
982
+ insertText: name,
983
+ sortText: "0_#{name}",
984
+ data: completion_data(name),
985
+ }
986
+ end
1047
987
 
1048
- if (func_binding = facts.functions[callable_name])
1049
- func_binding.type.params.each do |param|
1050
- next if already_provided.include?(param.name)
1051
- next unless prefix.empty? || param.name.start_with?(prefix)
988
+ facts.functions.each do |name, _binding|
989
+ next unless prefix.empty? || name.start_with?(prefix)
1052
990
  items << {
1053
- label: "#{param.name} = ",
1054
- kind: 14,
1055
- detail: "#{param.name}: #{param.type}",
1056
- insertText: "#{param.name} = ",
1057
- sortText: "0_#{param.name}",
1058
- data: completion_data(param.name),
991
+ label: name,
992
+ kind: 12,
993
+ detail: "function #{name}",
994
+ insertText: name,
995
+ sortText: "1_#{name}",
996
+ data: completion_data(name),
1059
997
  }
1060
998
  end
999
+
1000
+ frame = enclosing_completion_frame(facts, line + 1)
1001
+ if frame
1002
+ snapshot = latest_completion_snapshot(frame, line + 1, char + 1)
1003
+ if snapshot
1004
+ snapshot.bindings.each do |name, binding|
1005
+ next unless prefix.empty? || name.start_with?(prefix)
1006
+ next unless binding.respond_to?(:name) && binding.respond_to?(:type)
1007
+ items << {
1008
+ label: name,
1009
+ kind: 13,
1010
+ detail: "#{name}: #{binding.type}",
1011
+ insertText: name,
1012
+ sortText: "2_#{name}",
1013
+ data: completion_data(name),
1014
+ }
1015
+ end
1016
+ end
1017
+ end
1018
+
1019
+ items.empty? ? nil : items
1061
1020
  end
1062
1021
 
1063
- resolved_type = facts.types[callable_name]
1064
- if resolved_type.nil? && callable_name.include?('.')
1065
- parts = callable_name.split('.', 2)
1066
- mod_binding = facts.imports[parts[0]]
1067
- resolved_type = mod_binding&.types&.[](parts[1]) if mod_binding
1022
+ def named_argument_completions(facts, uri, line, char)
1023
+ return nil unless facts
1024
+
1025
+ content = @workspace.get_content(uri)
1026
+ return nil unless content
1027
+ lines = content.split("\n", -1)
1028
+ line_text = lines[line] || ''
1029
+ return nil if line_text.empty?
1030
+
1031
+ text_before = line_text[0...char]
1032
+
1033
+ call_match = text_before.match(/([\w.]+)\s*\(\s*(?:[^)]*,\s*)\s*$/)
1034
+ call_match ||= text_before.match(/([\w.]+)\s*\(\s*$/)
1035
+
1036
+ return nil unless call_match
1037
+
1038
+ callable_name = call_match[1]
1039
+ prefix_match = text_before.match(/(?:^|[,\s(])(\w*)$/)
1040
+ prefix = prefix_match ? prefix_match[1] : ''
1041
+
1042
+ already_provided = text_before.scan(/(\w+)\s*[=:]/).flatten.to_set
1043
+
1044
+ items = []
1045
+
1046
+ if (func_binding = facts.functions[callable_name])
1047
+ func_binding.type.params.each do |param|
1048
+ next if already_provided.include?(param.name)
1049
+ next unless prefix.empty? || param.name.start_with?(prefix)
1050
+ items << {
1051
+ label: "#{param.name} = ",
1052
+ kind: 14,
1053
+ detail: "#{param.name}: #{param.type}",
1054
+ insertText: "#{param.name} = ",
1055
+ sortText: "0_#{param.name}",
1056
+ data: completion_data(param.name),
1057
+ }
1058
+ end
1059
+ end
1060
+
1061
+ resolved_type = facts.types[callable_name]
1062
+ if resolved_type.nil? && callable_name.include?('.')
1063
+ parts = callable_name.split('.', 2)
1064
+ mod_binding = facts.imports[parts[0]]
1065
+ resolved_type = mod_binding&.types&.[](parts[1]) if mod_binding
1066
+ end
1067
+
1068
+ if resolved_type&.respond_to?(:fields) && !resolved_type.fields.empty?
1069
+ resolved_type.fields.each do |fname, ftype|
1070
+ next if already_provided.include?(fname)
1071
+ next unless prefix.empty? || fname.start_with?(prefix)
1072
+ items << {
1073
+ label: "#{fname} = ",
1074
+ kind: 8,
1075
+ detail: "#{fname}: #{ftype}",
1076
+ insertText: "#{fname} = ",
1077
+ sortText: "0_#{fname}",
1078
+ data: completion_data(fname),
1079
+ }
1080
+ end
1081
+ end
1082
+
1083
+ items.empty? ? nil : items
1068
1084
  end
1069
1085
 
1070
- if resolved_type&.respond_to?(:fields) && !resolved_type.fields.empty?
1071
- resolved_type.fields.each do |fname, ftype|
1072
- next if already_provided.include?(fname)
1073
- next unless prefix.empty? || fname.start_with?(prefix)
1086
+ def specialization_completions(facts, _uri, line, char)
1087
+ return nil unless facts
1088
+
1089
+ content = @workspace.get_content(_uri)
1090
+ return nil unless content
1091
+ lines = content.split("\n", -1)
1092
+ line_text = lines[line] || ''
1093
+ return nil if line_text.empty?
1094
+
1095
+ text_before = line_text[0...char]
1096
+ sp_match = text_before.match(/([\w.]+)\[(.*)$/)
1097
+ return nil unless sp_match
1098
+
1099
+ prefix = sp_match[2]
1100
+ items = []
1101
+
1102
+ facts.types.each do |name, _type|
1103
+ next unless prefix.empty? || name.start_with?(prefix)
1074
1104
  items << {
1075
- label: "#{fname} = ",
1076
- kind: 8,
1077
- detail: "#{fname}: #{ftype}",
1078
- insertText: "#{fname} = ",
1079
- sortText: "0_#{fname}",
1080
- data: completion_data(fname),
1105
+ label: name,
1106
+ kind: 5,
1107
+ detail: "type #{name}",
1108
+ insertText: name,
1109
+ sortText: "0_#{name}",
1110
+ data: completion_data(name),
1081
1111
  }
1082
1112
  end
1083
- end
1084
1113
 
1085
- items.empty? ? nil : items
1086
- end
1087
-
1088
- def specialization_completions(facts, _uri, line, char)
1089
- return nil unless facts
1090
-
1091
- content = @workspace.get_content(_uri)
1092
- return nil unless content
1093
- lines = content.split("\n", -1)
1094
- line_text = lines[line] || ''
1095
- return nil if line_text.empty?
1096
-
1097
- text_before = line_text[0...char]
1098
- sp_match = text_before.match(/([\w.]+)\[(.*)$/)
1099
- return nil unless sp_match
1100
-
1101
- prefix = sp_match[2]
1102
- items = []
1103
-
1104
- facts.types.each do |name, _type|
1105
- next unless prefix.empty? || name.start_with?(prefix)
1106
- items << {
1107
- label: name,
1108
- kind: 5,
1109
- detail: "type #{name}",
1110
- insertText: name,
1111
- sortText: "0_#{name}",
1112
- data: completion_data(name),
1113
- }
1114
+ items.empty? ? nil : items
1114
1115
  end
1115
-
1116
- items.empty? ? nil : items
1117
- end
1118
1116
  end
1119
1117
  end
1120
1118
  end