mt-lang 0.2.0

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 (258) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/AUTHORS +3 -0
  4. data/Gemfile +19 -0
  5. data/Gemfile.lock +82 -0
  6. data/LICENSE +21 -0
  7. data/README.md +1208 -0
  8. data/Rakefile +332 -0
  9. data/bin/mtc +8 -0
  10. data/bin/profile-mtc-checks +133 -0
  11. data/bin/tracy-profiler +0 -0
  12. data/docs/build-guide.md +536 -0
  13. data/docs/index.html +2778 -0
  14. data/docs/language-design.md +1519 -0
  15. data/docs/language-manual.md +1534 -0
  16. data/lib/milk_tea/base.rb +49 -0
  17. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
  18. data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
  19. data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
  20. data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
  21. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
  22. data/lib/milk_tea/bindings/bindgen.rb +207 -0
  23. data/lib/milk_tea/bindings/cli.rb +121 -0
  24. data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
  25. data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
  26. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
  27. data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
  28. data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
  29. data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
  30. data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
  31. data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
  32. data/lib/milk_tea/bindings/steamworks.rb +629 -0
  33. data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
  34. data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
  35. data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
  36. data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
  37. data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
  38. data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
  39. data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
  40. data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
  41. data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
  42. data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
  43. data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
  44. data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
  45. data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
  46. data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
  47. data/lib/milk_tea/bindings.rb +23 -0
  48. data/lib/milk_tea/core/ast.rb +311 -0
  49. data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
  50. data/lib/milk_tea/core/binding_types.rb +18 -0
  51. data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
  52. data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
  53. data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
  54. data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
  55. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
  56. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
  57. data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
  58. data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
  59. data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
  60. data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
  61. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
  62. data/lib/milk_tea/core/c_backend/statements.rb +563 -0
  63. data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
  64. data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
  65. data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
  66. data/lib/milk_tea/core/c_backend.rb +287 -0
  67. data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
  68. data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
  69. data/lib/milk_tea/core/compile_time.rb +329 -0
  70. data/lib/milk_tea/core/control_flow/builder.rb +582 -0
  71. data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
  72. data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
  73. data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
  74. data/lib/milk_tea/core/control_flow/graph.rb +90 -0
  75. data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
  76. data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
  77. data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
  78. data/lib/milk_tea/core/control_flow/termination.rb +31 -0
  79. data/lib/milk_tea/core/control_flow.rb +34 -0
  80. data/lib/milk_tea/core/cst.rb +48 -0
  81. data/lib/milk_tea/core/cst_builder.rb +19 -0
  82. data/lib/milk_tea/core/ir.rb +85 -0
  83. data/lib/milk_tea/core/keywords.rb +97 -0
  84. data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
  85. data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
  86. data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
  87. data/lib/milk_tea/core/lexer/indentation.rb +62 -0
  88. data/lib/milk_tea/core/lexer/numbers.rb +96 -0
  89. data/lib/milk_tea/core/lexer/recovery.rb +32 -0
  90. data/lib/milk_tea/core/lexer/strings.rb +167 -0
  91. data/lib/milk_tea/core/lexer/symbols.rb +71 -0
  92. data/lib/milk_tea/core/lexer/trivia.rb +53 -0
  93. data/lib/milk_tea/core/lexer.rb +430 -0
  94. data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
  95. data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
  96. data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
  97. data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
  98. data/lib/milk_tea/core/lowering/async.rb +714 -0
  99. data/lib/milk_tea/core/lowering/block.rb +1052 -0
  100. data/lib/milk_tea/core/lowering/calls.rb +1565 -0
  101. data/lib/milk_tea/core/lowering/declarations.rb +214 -0
  102. data/lib/milk_tea/core/lowering/dyn.rb +206 -0
  103. data/lib/milk_tea/core/lowering/events.rb +1054 -0
  104. data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
  105. data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
  106. data/lib/milk_tea/core/lowering/functions.rb +242 -0
  107. data/lib/milk_tea/core/lowering/loops.rb +1087 -0
  108. data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
  109. data/lib/milk_tea/core/lowering/proc.rb +419 -0
  110. data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
  111. data/lib/milk_tea/core/lowering/scans.rb +220 -0
  112. data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
  113. data/lib/milk_tea/core/lowering/utils.rb +1453 -0
  114. data/lib/milk_tea/core/lowering.rb +378 -0
  115. data/lib/milk_tea/core/module_binder.rb +181 -0
  116. data/lib/milk_tea/core/module_loader/errors.rb +21 -0
  117. data/lib/milk_tea/core/module_loader.rb +479 -0
  118. data/lib/milk_tea/core/module_path_resolver.rb +153 -0
  119. data/lib/milk_tea/core/module_roots.rb +88 -0
  120. data/lib/milk_tea/core/parser/attributes.rb +71 -0
  121. data/lib/milk_tea/core/parser/blocks.rb +119 -0
  122. data/lib/milk_tea/core/parser/declarations.rb +749 -0
  123. data/lib/milk_tea/core/parser/expressions.rb +624 -0
  124. data/lib/milk_tea/core/parser/recovery.rb +131 -0
  125. data/lib/milk_tea/core/parser/statements.rb +756 -0
  126. data/lib/milk_tea/core/parser/types.rb +271 -0
  127. data/lib/milk_tea/core/parser.rb +400 -0
  128. data/lib/milk_tea/core/prelude_installer.rb +29 -0
  129. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
  130. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
  131. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
  132. data/lib/milk_tea/core/pretty_printer.rb +17 -0
  133. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
  134. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
  135. data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
  136. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
  137. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
  138. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
  139. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
  140. data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
  141. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
  142. data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
  143. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
  144. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
  145. data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
  146. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
  147. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
  148. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
  149. data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
  150. data/lib/milk_tea/core/token.rb +26 -0
  151. data/lib/milk_tea/core/token_stream.rb +30 -0
  152. data/lib/milk_tea/core/types/layout.rb +243 -0
  153. data/lib/milk_tea/core/types/predicates.rb +609 -0
  154. data/lib/milk_tea/core/types/registry.rb +83 -0
  155. data/lib/milk_tea/core/types/types.rb +1696 -0
  156. data/lib/milk_tea/core/types/visitor.rb +442 -0
  157. data/lib/milk_tea/core.rb +25 -0
  158. data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
  159. data/lib/milk_tea/dap/protocol.rb +58 -0
  160. data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
  161. data/lib/milk_tea/dap/server/debug_map.rb +291 -0
  162. data/lib/milk_tea/dap/server/handlers.rb +374 -0
  163. data/lib/milk_tea/dap/server/launch.rb +261 -0
  164. data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
  165. data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
  166. data/lib/milk_tea/dap/server/utilities.rb +160 -0
  167. data/lib/milk_tea/dap/server/wire.rb +55 -0
  168. data/lib/milk_tea/dap/server.rb +131 -0
  169. data/lib/milk_tea/dap/session.rb +152 -0
  170. data/lib/milk_tea/dap.rb +6 -0
  171. data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
  172. data/lib/milk_tea/lsp/diagnostics.rb +611 -0
  173. data/lib/milk_tea/lsp/protocol.rb +104 -0
  174. data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
  175. data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
  176. data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
  177. data/lib/milk_tea/lsp/server/completion.rb +1099 -0
  178. data/lib/milk_tea/lsp/server/configuration.rb +167 -0
  179. data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
  180. data/lib/milk_tea/lsp/server/definition.rb +779 -0
  181. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
  182. data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
  183. data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
  184. data/lib/milk_tea/lsp/server/formatting.rb +575 -0
  185. data/lib/milk_tea/lsp/server/hover.rb +1465 -0
  186. data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
  187. data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
  188. data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
  189. data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
  190. data/lib/milk_tea/lsp/server/progress.rb +47 -0
  191. data/lib/milk_tea/lsp/server/references.rb +433 -0
  192. data/lib/milk_tea/lsp/server/rename.rb +598 -0
  193. data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
  194. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
  195. data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
  196. data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
  197. data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
  198. data/lib/milk_tea/lsp/server/utilities.rb +520 -0
  199. data/lib/milk_tea/lsp/server.rb +415 -0
  200. data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
  201. data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
  202. data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
  203. data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
  204. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
  205. data/lib/milk_tea/lsp/workspace/store.rb +154 -0
  206. data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
  207. data/lib/milk_tea/lsp/workspace.rb +127 -0
  208. data/lib/milk_tea/lsp.rb +13 -0
  209. data/lib/milk_tea/packages/atomic_write.rb +63 -0
  210. data/lib/milk_tea/packages/dependency_solver.rb +194 -0
  211. data/lib/milk_tea/packages/graph.rb +139 -0
  212. data/lib/milk_tea/packages/lock.rb +421 -0
  213. data/lib/milk_tea/packages/manager_cli.rb +485 -0
  214. data/lib/milk_tea/packages/manifest.rb +356 -0
  215. data/lib/milk_tea/packages/manifest_editor.rb +134 -0
  216. data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
  217. data/lib/milk_tea/packages/registry_store.rb +420 -0
  218. data/lib/milk_tea/packages/services.rb +41 -0
  219. data/lib/milk_tea/packages/source_cache.rb +71 -0
  220. data/lib/milk_tea/packages/source_fetcher.rb +124 -0
  221. data/lib/milk_tea/packages/source_resolver.rb +389 -0
  222. data/lib/milk_tea/packages/version.rb +164 -0
  223. data/lib/milk_tea/packages.rb +16 -0
  224. data/lib/milk_tea/tooling/asset_pack.rb +141 -0
  225. data/lib/milk_tea/tooling/build.rb +1124 -0
  226. data/lib/milk_tea/tooling/build_cache.rb +240 -0
  227. data/lib/milk_tea/tooling/cli.rb +2688 -0
  228. data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
  229. data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
  230. data/lib/milk_tea/tooling/debug_map.rb +248 -0
  231. data/lib/milk_tea/tooling/docs_app.rb +369 -0
  232. data/lib/milk_tea/tooling/error_formatter.rb +86 -0
  233. data/lib/milk_tea/tooling/formatter.rb +787 -0
  234. data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
  235. data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
  236. data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
  237. data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
  238. data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
  239. data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
  240. data/lib/milk_tea/tooling/linter/rules.rb +593 -0
  241. data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
  242. data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
  243. data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
  244. data/lib/milk_tea/tooling/linter.rb +798 -0
  245. data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
  246. data/lib/milk_tea/tooling/public/css/docs.css +166 -0
  247. data/lib/milk_tea/tooling/public/js/docs.js +94 -0
  248. data/lib/milk_tea/tooling/run.rb +408 -0
  249. data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
  250. data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
  251. data/lib/milk_tea/tooling/views/404.erb +7 -0
  252. data/lib/milk_tea/tooling/views/index.erb +87 -0
  253. data/lib/milk_tea/tooling/views/layout.erb +69 -0
  254. data/lib/milk_tea/tooling/views/module.erb +97 -0
  255. data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
  256. data/lib/milk_tea/tooling.rb +20 -0
  257. data/lib/milk_tea.rb +8 -0
  258. metadata +426 -0
@@ -0,0 +1,1099 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Server
6
+ module ServerCompletion
7
+ MAX_COMPLETION_ITEMS = 200
8
+
9
+ COMPLETION_KEYWORDS = %w[
10
+ let var const function async struct enum flags variant union
11
+ type interface opaque if while for match return import public static
12
+ extending external fn event attribute defer break continue pass
13
+ unsafe consuming implements include link foreign proc
14
+ editable in out inout as when inline await static_assert emit
15
+ ].freeze
16
+
17
+ TYPE_CONSTRUCTOR_KEYWORDS = %w[
18
+ ptr ref span array dyn Option Result Task SoA str_buffer const_ptr
19
+ ].freeze
20
+
21
+ private
22
+
23
+ def completion_data(name)
24
+ { uri: @current_completion_uri || '', name: }
25
+ end
26
+
27
+ def snippet_for_callable(label, params)
28
+ return "#{label}()" if params.empty?
29
+ return nil if params.length > 5
30
+
31
+ stops = params.each_with_index.map { |p, i| "${#{i + 1}:#{p.name}}" }
32
+ "#{label}(#{stops.join(', ')})"
33
+ end
34
+
35
+ def label_details_for_params(params_str, module_name = nil)
36
+ result = {}
37
+ result[:detail] = " (#{params_str})" if params_str && !params_str.empty?
38
+ result[:description] = module_name if module_name && !module_name.empty?
39
+ result.empty? ? nil : result
40
+ end
41
+
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
67
+
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
75
+
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
83
+
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
91
+
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
99
+
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 }
159
+ 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
+
164
+
165
+ # Enum/Flags member access: Color.RED, KeyboardKey.A, etc.
166
+ if type.is_a?(Types::EnumBase)
167
+ branch = 'enum-members'
168
+ 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),
178
+ }
179
+ end
180
+ end
181
+ item_count = items.length
182
+ return { isIncomplete: false, items: items }
183
+ end
184
+
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
+ }
199
+ end
200
+ end
201
+ item_count = items.length
202
+ return { isIncomplete: false, items: items }
203
+ end
204
+
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
+ }
219
+ end
220
+ end
221
+ item_count = items.length
222
+ return { isIncomplete: false, items: items } unless items.empty?
223
+ end
224
+
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 }
230
+ end
231
+ end
232
+
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 }
245
+ 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
+ end
254
+ end
255
+ end
256
+
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 }
263
+ end
264
+ end
265
+
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
289
+ end
290
+ result << item
291
+ end
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
300
+ end
301
+ return { isIncomplete: truncated, items: method_items }
302
+ end
303
+
304
+ branch = 'global'
305
+ items = measure_perf_stage(stages, 'build') do
306
+ result = []
307
+ function_docs_cache = {}
308
+
309
+ # Functions
310
+ facts.functions.each do |name, binding|
311
+ next unless prefix.empty? || name.start_with?(prefix)
312
+
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
+ }
322
+
323
+ if (ld = label_details_for_params(params_str, nil))
324
+ entry[:labelDetails] = ld
325
+ end
326
+
327
+ if (snippet = snippet_for_callable(name, binding.type.params))
328
+ entry[:insertText] = snippet
329
+ entry[:insertTextFormat] = 2
330
+ end
331
+
332
+ docs = completion_function_documentation(uri, name, cache: function_docs_cache)
333
+ unless docs.empty?
334
+ entry[:documentation] = {
335
+ kind: 'markdown',
336
+ value: docs,
337
+ }
338
+ end
339
+
340
+ result << entry
341
+ end
342
+
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)
348
+
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
364
+
365
+ # Imported modules
366
+ facts.imports.each do |name, module_binding|
367
+ next unless prefix.empty? || name.start_with?(prefix)
368
+
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
378
+
379
+ # Values
380
+ facts.values.each do |name, binding|
381
+ next unless prefix.empty? || name.start_with?(prefix)
382
+
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
392
+
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)
401
+
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
417
+ end
418
+ end
419
+
420
+ TYPE_CONSTRUCTOR_KEYWORDS.each do |tc|
421
+ next unless prefix.empty? || tc.start_with?(prefix)
422
+
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
+ }
431
+ end
432
+
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
+ }
444
+ 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
454
+ 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
+
464
+ def value_chain_completions(facts, dot_recv_path, lsp_line, lsp_char, prefix)
465
+ return nil unless dot_recv_path&.include?('.')
466
+
467
+ chain_segments = dot_recv_path.split('.')
468
+ return nil if chain_segments.length < 2
469
+
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
473
+
474
+ current_type = first_type
475
+ chain_segments[1..].each do |seg|
476
+ field_owner = project_field_receiver_type_for_completion(current_type)
477
+ if field_owner.respond_to?(:field) && (field_type = field_owner.field(seg))
478
+ current_type = field_type
479
+ else
480
+ return nil
481
+ end
482
+ end
483
+
484
+ items = completion_items_for_value_receiver(facts, current_type, prefix)
485
+ items.empty? ? nil : items
486
+ end
487
+
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
510
+ end
511
+ item
512
+ end
513
+ end
514
+
515
+ def resolve_nested_type_binding(facts, name)
516
+ return { qualified_name: nil, type: nil } unless facts
517
+
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 }
522
+ end
523
+ end
524
+ { qualified_name: nil, type: nil }
525
+ end
526
+
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
533
+
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]
536
+
537
+ return nil unless receiver_path&.include?('.')
538
+
539
+ module_alias, type_name = receiver_path.split('.', 2)
540
+ return nil unless module_alias && type_name
541
+
542
+ module_binding = facts.imports[module_alias]
543
+ return nil unless module_binding
544
+
545
+ type = module_binding.types[type_name]
546
+ return nil unless type
547
+
548
+ { label: receiver_path, type:, module_name: module_binding.name }
549
+ end
550
+
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
554
+
555
+ binding = static_method_binding_for_receiver(facts, receiver_info[:type], method_name)
556
+ return nil unless binding
557
+
558
+ receiver_info.merge(binding:)
559
+ end
560
+
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
565
+
566
+ binding
567
+ end
568
+
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)
573
+
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
579
+
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
593
+
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
597
+
598
+ binding
599
+ end
600
+
601
+ def static_type_method_references(target, include_declaration:)
602
+ name = target[:binding].name
603
+ target_uri = target[:location][:uri]
604
+
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?
611
+ end
612
+ end
613
+
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)
624
+ end
625
+ end
626
+ end
627
+
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
631
+
632
+ facts = @workspace.get_facts(ref[:uri])
633
+ return false unless facts
634
+
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
640
+
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
645
+
646
+ def location_matches_reference?(location, ref)
647
+ return false unless location
648
+
649
+ same_location?(location, {
650
+ uri: ref[:uri],
651
+ range: ref[:range]
652
+ })
653
+ end
654
+
655
+ def same_location?(left, right)
656
+ return false unless left && right
657
+
658
+ left[:uri] == right[:uri] && left[:range] == right[:range]
659
+ end
660
+
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
664
+
665
+ receiver_index = previous_non_trivia_token_index(tokens, dot_index)
666
+ return nil unless receiver_index
667
+
668
+ receiver = tokens[receiver_index]
669
+ return nil unless receiver.type == :identifier
670
+
671
+ { receiver: receiver.lexeme }
672
+ end
673
+
674
+ def completion_items_for_value_receiver(facts, receiver_type, prefix)
675
+ items = []
676
+
677
+ field_receiver_type = project_field_receiver_type_for_completion(receiver_type)
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)
681
+
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
+ }
690
+ end
691
+ end
692
+
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
713
+ end
714
+ items << item
715
+ end
716
+
717
+ items
718
+ end
719
+
720
+ def methods_for_receiver_type(facts, receiver_type)
721
+ methods = {}
722
+ return methods unless receiver_type
723
+
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
727
+
728
+ receiver_candidates.each do |candidate|
729
+ facts.methods.fetch(candidate, {}).each do |name, binding|
730
+ methods[name] ||= binding
731
+ end
732
+ end
733
+
734
+ facts.imports.each_value do |module_binding|
735
+ receiver_candidates.each do |candidate|
736
+ module_binding.methods.fetch(candidate, {}).each do |name, binding|
737
+ methods[name] ||= binding
738
+ end
739
+ end
740
+ end
741
+ methods
742
+ end
743
+
744
+ def method_dispatch_receiver_type_for_completion(receiver_type)
745
+ return receiver_type.definition if receiver_type.is_a?(Types::StructInstance)
746
+
747
+ if receiver_type.is_a?(Types::Nullable)
748
+ dispatch_base_type = method_dispatch_receiver_type_for_completion(receiver_type.base)
749
+ return receiver_type if dispatch_base_type == receiver_type.base
750
+
751
+ return Types::Registry.nullable(dispatch_base_type)
752
+ end
753
+
754
+ return receiver_type unless receiver_type.is_a?(Types::GenericInstance)
755
+
756
+ dispatch_receiver_type = Types::Registry.generic_instance(
757
+ receiver_type.name,
758
+ receiver_type.arguments.each_with_index.map do |argument, index|
759
+ argument.is_a?(Types::LiteralTypeArg) ? argument : Types::TypeVar.new("__receiver_arg#{index}")
760
+ end,
761
+ )
762
+
763
+ dispatch_receiver_type == receiver_type ? receiver_type : dispatch_receiver_type
764
+ end
765
+
766
+ def project_field_receiver_type_for_completion(type)
767
+ project_receiver_type_for_completion(type)
768
+ end
769
+
770
+ def project_method_receiver_type_for_completion(type)
771
+ project_receiver_type_for_completion(type)
772
+ end
773
+
774
+ def project_receiver_type_for_completion(type)
775
+ return type.arguments.first if ref_type_name?(type)
776
+ return type.arguments.first if pointer_type_name?(type)
777
+
778
+ type
779
+ end
780
+
781
+ def field_owner_type(receiver_type)
782
+ aggregate_type = project_field_receiver_type_for_completion(receiver_type)
783
+ return aggregate_type.definition if aggregate_type.is_a?(Types::StructInstance)
784
+
785
+ aggregate_type
786
+ end
787
+
788
+ def ref_type_name?(type)
789
+ type.is_a?(Types::GenericInstance) && type.name == 'ref' && type.arguments.length == 1
790
+ end
791
+
792
+ def pointer_type_name?(type)
793
+ type.is_a?(Types::GenericInstance) && %w[ptr const_ptr].include?(type.name) && type.arguments.length == 1
794
+ end
795
+
796
+ def handle_completion_resolve(params)
797
+ data = params['data']
798
+ return params unless data.is_a?(Hash)
799
+
800
+ name = data['name']
801
+ return params if name.to_s.empty?
802
+
803
+ uri = data['uri'] || ''
804
+ definition_entry = @workspace.find_definition_token_global(name, preferred_uri: uri)
805
+ return params unless definition_entry
806
+
807
+ doc_comment = @workspace.doc_comment_data_for_definition(definition_entry[:uri], definition_entry[:token])
808
+ docs = signature_help_markdown_for_doc_comment(doc_comment)
809
+ return params if docs.empty?
810
+
811
+ params.merge('documentation' => { 'kind' => 'markdown', 'value' => docs })
812
+ rescue StandardError => e
813
+ warn "Error in completion resolve handler: #{e.message}"
814
+ params
815
+ end
816
+
817
+ def import_completions(uri, lsp_line, lsp_char)
818
+ content = @workspace.get_content(uri)
819
+ lines = content.split("\n", -1)
820
+ line = lines[lsp_line] || ''
821
+ stripped = line.lstrip
822
+ return nil unless stripped.start_with?('import ')
823
+
824
+ import_kw_pos = line.index('import ')
825
+ after_import = line[(import_kw_pos + 7)..].to_s
826
+
827
+ if (as_match = after_import.match(/\s+as\s+/))
828
+ alias_pos = as_match.begin(0)
829
+ cursor_in_import = lsp_char - import_kw_pos - 7
830
+ return nil if cursor_in_import > alias_pos
831
+ after_import = after_import[0...alias_pos]
832
+ end
833
+
834
+ cursor_in_import = lsp_char - import_kw_pos - 7
835
+ cursor_in_import = [[cursor_in_import, after_import.length].min, 0].max
836
+
837
+ path_typed = after_import[0...cursor_in_import].strip
838
+
839
+ segments = path_typed.split('.', -1)
840
+ dir_segments = segments[0...-1]
841
+ filter = segments.last.to_s
842
+
843
+ current_path = uri_to_path(uri)
844
+ return nil unless current_path
845
+
846
+ roots = MilkTea::ModuleRoots.roots_for_path(current_path)
847
+ fs_dir = dir_segments.join(File::SEPARATOR)
848
+
849
+ modules = {}
850
+ filter_lower = filter.downcase
851
+
852
+ roots.each do |root|
853
+ search_dir = fs_dir.empty? ? root : File.join(root, fs_dir)
854
+ next unless File.directory?(search_dir)
855
+
856
+ Dir.children(search_dir).sort.each do |name|
857
+ next if name.start_with?('.')
858
+ full_path = File.join(search_dir, name)
859
+
860
+ if name.end_with?('.mt')
861
+ mod_name = name.delete_suffix('.mt')
862
+ next if mod_name.start_with?('.')
863
+ next if full_path == current_path
864
+ next unless filter.empty? || mod_name.downcase.start_with?(filter_lower)
865
+ modules[mod_name] = mod_name
866
+ elsif File.directory?(full_path) && module_dir_contains_mt?(full_path)
867
+ next unless filter.empty? || name.downcase.start_with?(filter_lower)
868
+ modules[name] = name
869
+ end
870
+ end
871
+ end
872
+
873
+ return nil if modules.empty?
874
+
875
+ modules.values.sort.map do |mod_name|
876
+ {
877
+ label: mod_name,
878
+ kind: 2,
879
+ detail: "module #{mod_name}",
880
+ insertText: mod_name,
881
+ sortText: "0_#{mod_name}",
882
+ data: completion_data(mod_name),
883
+ }
884
+ end
885
+ end
886
+
887
+ def module_dir_contains_mt?(dir)
888
+ Dir.children(dir).any? do |name|
889
+ next false if name.start_with?('.')
890
+ full = File.join(dir, name)
891
+ name.end_with?('.mt') || (File.directory?(full) && module_dir_contains_mt?(full))
892
+ end
893
+ end
894
+
895
+ def attribute_completions(facts, uri, line, char)
896
+ content = @workspace.get_content(uri)
897
+ return nil unless content
898
+ lines = content.split("\n", -1)
899
+ line_text = lines[line] || ''
900
+ return nil if line_text.empty?
901
+
902
+ attr_match = line_text[0...char].match(/@\[(\w*)$/)
903
+ return nil unless attr_match
904
+
905
+ prefix = attr_match[1]
906
+ items = []
907
+
908
+ %w[packed align deprecated].each do |name|
909
+ next unless prefix.empty? || name.start_with?(prefix)
910
+ items << {
911
+ label: name,
912
+ kind: 14,
913
+ detail: "built-in attribute #{name}",
914
+ insertText: name,
915
+ sortText: "0_#{name}",
916
+ data: completion_data(name),
917
+ }
918
+ end
919
+
920
+ if facts
921
+ facts.attributes&.each do |name, _binding|
922
+ next unless prefix.empty? || name.start_with?(prefix)
923
+ items << {
924
+ label: name,
925
+ kind: 14,
926
+ detail: "attribute #{name}",
927
+ insertText: name,
928
+ sortText: "1_#{name}",
929
+ data: completion_data(name),
930
+ }
931
+ end
932
+ end
933
+
934
+ items.empty? ? nil : items
935
+ end
936
+
937
+ def format_string_completions(facts, uri, line, char)
938
+ return nil unless facts
939
+
940
+ content = @workspace.get_content(uri)
941
+ return nil unless content
942
+ lines = content.split("\n", -1)
943
+ line_text = lines[line] || ''
944
+ return nil if line_text.empty?
945
+
946
+ text_before = line_text[0...char]
947
+ open_pos = text_before.rindex('#{')
948
+ return nil unless open_pos
949
+
950
+ close_pos = text_before.index('}', open_pos)
951
+ return nil if close_pos && close_pos < char
952
+
953
+ prefix = text_before[(open_pos + 2)..] || ''
954
+ items = []
955
+
956
+ facts.values.each do |name, binding|
957
+ next unless prefix.empty? || name.start_with?(prefix)
958
+ items << {
959
+ label: name,
960
+ kind: 13,
961
+ detail: "#{name}: #{binding.type}",
962
+ insertText: name,
963
+ sortText: "0_#{name}",
964
+ data: completion_data(name),
965
+ }
966
+ end
967
+
968
+ facts.functions.each do |name, _binding|
969
+ next unless prefix.empty? || name.start_with?(prefix)
970
+ items << {
971
+ label: name,
972
+ kind: 12,
973
+ detail: "function #{name}",
974
+ insertText: name,
975
+ sortText: "1_#{name}",
976
+ data: completion_data(name),
977
+ }
978
+ end
979
+
980
+ frame = enclosing_completion_frame(facts, line + 1)
981
+ if frame
982
+ snapshot = latest_completion_snapshot(frame, line + 1, char + 1)
983
+ if snapshot
984
+ snapshot.bindings.each do |name, binding|
985
+ next unless prefix.empty? || name.start_with?(prefix)
986
+ next unless binding.respond_to?(:name) && binding.respond_to?(:type)
987
+ items << {
988
+ label: name,
989
+ kind: 13,
990
+ detail: "#{name}: #{binding.type}",
991
+ insertText: name,
992
+ sortText: "2_#{name}",
993
+ data: completion_data(name),
994
+ }
995
+ end
996
+ end
997
+ end
998
+
999
+ items.empty? ? nil : items
1000
+ end
1001
+
1002
+ def named_argument_completions(facts, uri, line, char)
1003
+ return nil unless facts
1004
+
1005
+ content = @workspace.get_content(uri)
1006
+ return nil unless content
1007
+ lines = content.split("\n", -1)
1008
+ line_text = lines[line] || ''
1009
+ return nil if line_text.empty?
1010
+
1011
+ text_before = line_text[0...char]
1012
+
1013
+ call_match = text_before.match(/([\w.]+)\s*\(\s*(?:[^)]*,\s*)\s*$/)
1014
+ call_match ||= text_before.match(/([\w.]+)\s*\(\s*$/)
1015
+
1016
+ return nil unless call_match
1017
+
1018
+ callable_name = call_match[1]
1019
+ prefix_match = text_before.match(/(?:^|[,\s(])(\w*)$/)
1020
+ prefix = prefix_match ? prefix_match[1] : ''
1021
+
1022
+ already_provided = text_before.scan(/(\w+)\s*[=:]/).flatten.to_set
1023
+
1024
+ items = []
1025
+
1026
+ if (func_binding = facts.functions[callable_name])
1027
+ func_binding.type.params.each do |param|
1028
+ next if already_provided.include?(param.name)
1029
+ next unless prefix.empty? || param.name.start_with?(prefix)
1030
+ items << {
1031
+ label: "#{param.name} = ",
1032
+ kind: 14,
1033
+ detail: "#{param.name}: #{param.type}",
1034
+ insertText: "#{param.name} = ",
1035
+ sortText: "0_#{param.name}",
1036
+ data: completion_data(param.name),
1037
+ }
1038
+ end
1039
+ end
1040
+
1041
+ resolved_type = facts.types[callable_name]
1042
+ if resolved_type.nil? && callable_name.include?('.')
1043
+ parts = callable_name.split('.', 2)
1044
+ mod_binding = facts.imports[parts[0]]
1045
+ resolved_type = mod_binding&.types&.[](parts[1]) if mod_binding
1046
+ end
1047
+
1048
+ if resolved_type&.respond_to?(:fields) && !resolved_type.fields.empty?
1049
+ resolved_type.fields.each do |fname, ftype|
1050
+ next if already_provided.include?(fname)
1051
+ next unless prefix.empty? || fname.start_with?(prefix)
1052
+ items << {
1053
+ label: "#{fname} = ",
1054
+ kind: 8,
1055
+ detail: "#{fname}: #{ftype}",
1056
+ insertText: "#{fname} = ",
1057
+ sortText: "0_#{fname}",
1058
+ data: completion_data(fname),
1059
+ }
1060
+ end
1061
+ end
1062
+
1063
+ items.empty? ? nil : items
1064
+ end
1065
+
1066
+ def specialization_completions(facts, _uri, line, char)
1067
+ return nil unless facts
1068
+
1069
+ content = @workspace.get_content(_uri)
1070
+ return nil unless content
1071
+ lines = content.split("\n", -1)
1072
+ line_text = lines[line] || ''
1073
+ return nil if line_text.empty?
1074
+
1075
+ text_before = line_text[0...char]
1076
+ sp_match = text_before.match(/([\w.]+)\[(.*)$/)
1077
+ return nil unless sp_match
1078
+
1079
+ prefix = sp_match[2]
1080
+ items = []
1081
+
1082
+ facts.types.each do |name, _type|
1083
+ next unless prefix.empty? || name.start_with?(prefix)
1084
+ items << {
1085
+ label: name,
1086
+ kind: 5,
1087
+ detail: "type #{name}",
1088
+ insertText: name,
1089
+ sortText: "0_#{name}",
1090
+ data: completion_data(name),
1091
+ }
1092
+ end
1093
+
1094
+ items.empty? ? nil : items
1095
+ end
1096
+ end
1097
+ end
1098
+ end
1099
+ end