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,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Server
6
+ module ServerInlayHints
7
+ private
8
+
9
+ def handle_inlay_hint(params)
10
+ uri = params.dig('textDocument', 'uri')
11
+ range = params['range'] || {}
12
+ start_line = range.dig('start', 'line') || 0
13
+ start_char = range.dig('start', 'character') || 0
14
+ end_line = range.dig('end', 'line') || 0
15
+ end_char = range.dig('end', 'character') || 0
16
+
17
+ content = @workspace.get_content(uri)
18
+ return [] if content && skip_expensive_work_reason(uri, content)
19
+
20
+ facts = @workspace.get_facts(uri)
21
+ tokens = @workspace.get_tokens(uri)
22
+ return [] unless facts && tokens
23
+
24
+ hints = []
25
+
26
+ # Parameter-name hints at call sites
27
+ hints.concat(collect_parameter_name_hints(tokens, facts, start_line, start_char, end_line, end_char))
28
+
29
+ # Type annotation hints for inferred locals
30
+ hints.concat(collect_inferred_type_hints(facts, start_line, start_char, end_line, end_char))
31
+
32
+ # Return type hints for inferred return types
33
+ hints.concat(collect_inferred_return_hints(facts, start_line, start_char, end_line, end_char))
34
+
35
+ hints
36
+ rescue StandardError => e
37
+ warn "Error in inlayHint handler: #{e.message}"
38
+ []
39
+ end
40
+
41
+ def collect_parameter_name_hints(tokens, facts, start_line, start_char, end_line, end_char)
42
+ hints = []
43
+ i = 0
44
+ while i < tokens.length - 1
45
+ callee = tokens[i]
46
+ next_tok = tokens[i + 1]
47
+
48
+ prev_tok = i > 0 ? tokens[i - 1] : nil
49
+
50
+ if callee.type == :identifier && prev_tok&.type != :function && prev_tok&.type != :dot
51
+ binding = nil
52
+ lparen_index = nil
53
+
54
+ if next_tok&.type == :lparen
55
+ binding = facts.functions[callee.lexeme]
56
+ lparen_index = i + 1
57
+ elsif next_tok&.type == :dot
58
+ member = tokens[i + 2]
59
+ member_lparen = tokens[i + 3]
60
+ if member&.type == :identifier && member_lparen&.type == :lparen
61
+ module_binding = facts.imports[callee.lexeme]
62
+ binding = module_binding&.functions&.[](member.lexeme)
63
+ lparen_index = i + 3
64
+ end
65
+ end
66
+
67
+ if binding && lparen_index
68
+ arg_starts, closing_index = collect_call_argument_starts(tokens, lparen_index)
69
+ params_list = binding.type.params
70
+
71
+ arg_starts.each_with_index do |arg_tok, index|
72
+ break if index >= params_list.length
73
+ next unless position_in_range?(arg_tok.line - 1, arg_tok.column - 1, start_line, start_char, end_line, end_char)
74
+ next if self_describing_argument_expression?(tokens, arg_tok)
75
+ param_name = params_list[index].name
76
+ next if arg_tok.type == :identifier && arg_tok.lexeme == param_name
77
+
78
+ hints << {
79
+ position: { line: arg_tok.line - 1, character: arg_tok.column - 1 },
80
+ label: "#{params_list[index].name}: ",
81
+ kind: 2,
82
+ paddingRight: true
83
+ }
84
+ end
85
+
86
+ i = closing_index if closing_index
87
+ end
88
+ end
89
+
90
+ i += 1
91
+ end
92
+ hints
93
+ end
94
+
95
+ def collect_inferred_type_hints(facts, start_line, start_char, end_line, end_char)
96
+ hints = []
97
+ collect_local_decls(facts.ast).each do |decl|
98
+ next unless decl.type.nil?
99
+ next unless position_in_range?(decl.line - 1, decl.column - 1, start_line, start_char, end_line, end_char)
100
+
101
+ binding = facts.values[decl.name]
102
+ next unless binding
103
+
104
+ display_type = describe_type_for_hint(binding.storage_type)
105
+ next unless display_type
106
+
107
+ hints << {
108
+ position: { line: decl.line - 1, character: decl.column - 1 + decl.name.length },
109
+ label: ": #{display_type}",
110
+ kind: 1,
111
+ paddingRight: false
112
+ }
113
+ end
114
+ hints
115
+ end
116
+
117
+ def collect_inferred_return_hints(facts, start_line, start_char, end_line, end_char)
118
+ hints = []
119
+ collect_function_defs(facts.ast).each do |func|
120
+ next unless func.return_type.nil?
121
+ next unless position_in_range?(func.line - 1, func.column - 1, start_line, start_char, end_line, end_char)
122
+
123
+ binding = facts.functions[func.name]
124
+ next unless binding
125
+
126
+ return_type = binding.type.return_type
127
+ next unless return_type
128
+
129
+ display_type = describe_type_for_hint(return_type)
130
+ next unless display_type
131
+ next if display_type == "void"
132
+
133
+ label = "-> #{display_type}"
134
+ hints << {
135
+ position: { line: func.line - 1, character: func.column - 1 + func.name.length },
136
+ label: label,
137
+ kind: 1,
138
+ paddingRight: false
139
+ }
140
+ end
141
+ hints
142
+ end
143
+
144
+ def collect_local_decls(ast_node)
145
+ results = []
146
+ case ast_node
147
+ when Array
148
+ ast_node.each { |n| results.concat(collect_local_decls(n)) }
149
+ when AST::SourceFile
150
+ ast_node.declarations.each { |d| results.concat(collect_local_decls(d)) }
151
+ when AST::FunctionDef
152
+ results.concat(collect_local_decls(ast_node.body)) if ast_node.body
153
+ when AST::MethodDef
154
+ results.concat(collect_local_decls(ast_node.body)) if ast_node.body
155
+ when AST::ExtendingBlock
156
+ ast_node.methods.each { |m| results.concat(collect_local_decls(m)) }
157
+ when AST::IfStmt
158
+ ast_node.branches.each { |b| results.concat(collect_local_decls(b.body)) }
159
+ results.concat(collect_local_decls(ast_node.else_body)) if ast_node.else_body
160
+ when AST::MatchStmt
161
+ ast_node.arms.each { |a| results.concat(collect_local_decls(a.body)) }
162
+ when AST::ForStmt
163
+ results.concat(collect_local_decls(ast_node.body))
164
+ when AST::WhileStmt
165
+ results.concat(collect_local_decls(ast_node.body))
166
+ when AST::UnsafeStmt
167
+ results.concat(collect_local_decls(ast_node.body))
168
+ when AST::LocalDecl
169
+ results << ast_node
170
+ when AST::VariantArm
171
+ # no body
172
+ end
173
+ results
174
+ end
175
+
176
+ def collect_function_defs(ast_node)
177
+ results = []
178
+ case ast_node
179
+ when Array
180
+ ast_node.each { |n| results.concat(collect_function_defs(n)) }
181
+ when AST::SourceFile
182
+ ast_node.declarations.each do |d|
183
+ case d
184
+ when AST::FunctionDef
185
+ results << d
186
+ when AST::ExtendingBlock
187
+ results.concat(d.methods)
188
+ end
189
+ end
190
+ end
191
+ results
192
+ end
193
+
194
+ def describe_type_for_hint(type)
195
+ return nil unless type
196
+
197
+ type.to_s
198
+ rescue StandardError
199
+ nil
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Server
6
+ module ServerLifecycle
7
+ private
8
+
9
+ def handle_initialize(params)
10
+ @pull_diagnostics_active = true
11
+ if @_initialize_called
12
+ @_indexing_thread&.join rescue nil
13
+ @_indexing_thread = nil
14
+ @workspace.reset
15
+ @format_mode = :tidy
16
+ @dependency_resolution_mode = :auto
17
+ @platform_override = nil
18
+ @diagnostic_report_cache.clear
19
+ @workspace_diagnostic_cache.clear
20
+ drain_diagnostics_queue
21
+ @diagnostics_pending.clear
22
+ @diagnostics_enqueued.clear
23
+ @diagnostics_last_scheduled_hash.clear
24
+ @diagnostics_generation.clear
25
+ @semantic_tokens_cache.clear
26
+ @semantic_tokens_delta_cache.clear
27
+ @fixall_cache.clear
28
+ @definition_file_token_cache.clear
29
+ @definition_file_ast_cache.clear
30
+ MilkTea::Types::Registry.reset!
31
+ end
32
+ @_initialize_called = true
33
+ @root_uri = params['rootUri']
34
+ @client_capabilities = params['capabilities'] || {}
35
+ @workspace.workspace_root_path = uri_to_path(@root_uri)
36
+ negotiate_position_encoding(params)
37
+ apply_configuration_settings(params['initializationOptions'])
38
+ apply_configuration_settings(params['settings'])
39
+ {
40
+ capabilities: {
41
+ positionEncoding: @position_encoding,
42
+ textDocumentSync: {
43
+ openClose: true,
44
+ change: 2,
45
+ save: { includeText: true },
46
+ willSaveWaitUntil: true
47
+ },
48
+ hoverProvider: true,
49
+ definitionProvider: true,
50
+ declarationProvider: true,
51
+ typeDefinitionProvider: true,
52
+ implementationProvider: true,
53
+ referencesProvider: true,
54
+ documentLinkProvider: {
55
+ resolveProvider: true
56
+ },
57
+ documentHighlightProvider: true,
58
+ documentSymbolProvider: true,
59
+ documentFormattingProvider: true,
60
+ documentRangeFormattingProvider: true,
61
+ foldingRangeProvider: true,
62
+ callHierarchyProvider: true,
63
+ typeHierarchyProvider: true,
64
+ selectionRangeProvider: true,
65
+ linkedEditingRangeProvider: true,
66
+ executeCommandProvider: {
67
+ commands: ['mtc.restartServer']
68
+ },
69
+ documentOnTypeFormattingProvider: {
70
+ firstTriggerCharacter: "\n",
71
+ moreTriggerCharacter: []
72
+ },
73
+ signatureHelpProvider: {
74
+ triggerCharacters: ['(', ','],
75
+ retriggerCharacters: [',']
76
+ },
77
+ codeActionProvider: {
78
+ codeActionKinds: ['quickFix', 'source.fixAll']
79
+ },
80
+ codeLensProvider: {
81
+ resolveProvider: true
82
+ },
83
+ inlayHintProvider: true,
84
+ semanticTokensProvider: {
85
+ legend: {
86
+ tokenTypes: SEMANTIC_TOKEN_TYPES,
87
+ tokenModifiers: SEMANTIC_TOKEN_MODIFIERS
88
+ },
89
+ full: { delta: true },
90
+ range: true
91
+ },
92
+ completionProvider: {
93
+ triggerCharacters: ['.', '(', ' '],
94
+ resolveProvider: true
95
+ },
96
+ renameProvider: { prepareProvider: true },
97
+ workspaceSymbolProvider: true,
98
+ diagnosticProvider: {
99
+ interFileDependencies: true,
100
+ workspaceDiagnostics: true
101
+ },
102
+ workspace: {
103
+ workspaceFolders: {
104
+ supported: true,
105
+ changeNotifications: true,
106
+ },
107
+ fileOperations: {
108
+ willRename: {
109
+ filters: [{ pattern: { glob: '**/*.mt' }, scheme: 'file' }]
110
+ }
111
+ },
112
+ didChangeWatchedFiles: {
113
+ dynamicRegistration: true
114
+ }
115
+ }
116
+ }
117
+ }
118
+ end
119
+
120
+ def negotiate_position_encoding(params)
121
+ client_encodings = params.dig('capabilities', 'general', 'positionEncodings')
122
+ if client_encodings.is_a?(Array)
123
+ @position_encoding = if client_encodings.include?('utf-8')
124
+ 'utf-8'
125
+ elsif client_encodings.include?('utf-32')
126
+ 'utf-32'
127
+ else
128
+ 'utf-16'
129
+ end
130
+ else
131
+ @position_encoding = 'utf-16'
132
+ end
133
+ Diagnostics.position_encoding = @position_encoding
134
+ end
135
+
136
+ def handle_initialized(_params)
137
+ pull_client_configuration if @client_capabilities&.dig('workspace', 'configuration')
138
+ return nil unless @root_uri
139
+
140
+ @_indexing_thread = Thread.new do
141
+ progress = create_progress(title: 'Milk Tea LSP: Indexing workspace', message: 'Scanning source files...')
142
+ @workspace.index_workspace(@root_uri) { |pct, msg| progress.report(percentage: pct, message: msg) }
143
+ document_count = @workspace.all_documents.length
144
+ progress.done(message: "#{document_count} document#{document_count == 1 ? '' : 's'} indexed")
145
+ log_message(:info, "Milk Tea LSP ready — #{document_count} document#{document_count == 1 ? '' : 's'} indexed")
146
+ refresh_client_semantic_tokens if document_count > 0
147
+ end
148
+ nil
149
+ end
150
+
151
+ def handle_did_change_configuration(params)
152
+ apply_configuration_settings(params['settings'])
153
+ nil
154
+ end
155
+
156
+ def handle_cancel_request(params)
157
+ request_id = params.is_a?(Hash) ? (params['id'] || params[:id]) : nil
158
+ return nil if request_id.nil?
159
+
160
+ @cancelled_requests_mutex.synchronize do
161
+ @cancelled_request_ids << request_id
162
+ end
163
+ nil
164
+ end
165
+
166
+ def handle_did_change_workspace_folders(params)
167
+ event = params['event'] || {}
168
+ added = event['added'] || []
169
+ removed = event['removed'] || []
170
+
171
+ removed_uris = removed.filter_map { |folder| folder.is_a?(Hash) ? folder['uri'] : nil }
172
+ added_uris = added.filter_map { |folder| folder.is_a?(Hash) ? folder['uri'] : nil }
173
+
174
+ if removed_uris.include?(@root_uri)
175
+ @root_uri = added_uris.first
176
+ elsif @root_uri.nil?
177
+ @root_uri = added_uris.first
178
+ end
179
+
180
+ @workspace.workspace_root_path = uri_to_path(@root_uri)
181
+ return nil unless @root_uri
182
+
183
+ uris = @workspace.open_document_uris.dup
184
+ Thread.new do
185
+ progress = create_progress(title: 'Milk Tea LSP: Reindexing workspace', message: 'Scanning source files...')
186
+ @workspace.index_workspace(@root_uri) { |pct, msg| progress.report(percentage: pct, message: msg) }
187
+ doc_count = @workspace.all_documents.length
188
+ progress.done(message: "#{doc_count} document#{doc_count == 1 ? '' : 's'} indexed")
189
+
190
+ uris.each do |uri|
191
+ schedule_diagnostics(uri, force: true, lint_tier: :full) unless @workspace.background_document?(uri)
192
+ end
193
+ end
194
+ nil
195
+ end
196
+
197
+ def handle_will_rename_files(params)
198
+ files = params['files'] || []
199
+ files.each do |file|
200
+ old_uri = file['oldUri']
201
+ new_uri = file['newUri']
202
+ next unless old_uri && new_uri
203
+
204
+ @workspace.rename_indexed_file(old_uri, new_uri)
205
+ end
206
+ nil
207
+ end
208
+
209
+ def handle_shutdown(_params)
210
+ stop_diagnostics_workers
211
+ @workspace.shutdown
212
+ nil
213
+ end
214
+
215
+ def handle_exit(_params)
216
+ stop_diagnostics_workers
217
+ @workspace.shutdown
218
+ exit(0)
219
+ end
220
+
221
+ SEMANTIC_TOKENS_REFRESH_THROTTLE_MS = 500
222
+
223
+ def refresh_client_semantic_tokens
224
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
225
+ @semantic_tokens_last_refresh ||= 0.0
226
+ return if (now - @semantic_tokens_last_refresh) * 1000 < SEMANTIC_TOKENS_REFRESH_THROTTLE_MS
227
+
228
+ @semantic_tokens_last_refresh = now
229
+ @protocol.write_notification('workspace/semanticTokens/refresh', nil)
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Server
6
+ module ServerLinkedEditingRange
7
+ private
8
+
9
+ def handle_linked_editing_range(params)
10
+ uri = params.dig("textDocument", "uri")
11
+ lsp_line = params.dig("position", "line")
12
+ lsp_char = params.dig("position", "character")
13
+ return nil unless uri && lsp_line && lsp_char
14
+
15
+ token = @workspace.find_token_at(uri, lsp_line, lsp_char)
16
+ return nil unless token&.type == :identifier
17
+
18
+ facts = @workspace.get_facts(uri)
19
+ return nil unless facts
20
+
21
+ binding_id = rename_target_binding_id(uri, token, lsp_line, lsp_char, facts)
22
+ return nil unless binding_id
23
+ return nil unless local_binding_id?(uri, facts, binding_id)
24
+
25
+ ranges = scoped_binding_occurrence_ranges(uri, token.lexeme, facts, binding_id, include_declaration: true)
26
+ return nil if ranges.empty?
27
+
28
+ {
29
+ ranges: ranges.map do |r|
30
+ {
31
+ start: { line: r[:line] - 1, character: r[:column] - 1 },
32
+ end: { line: r[:line] - 1, character: r[:column] - 1 + r[:length] },
33
+ }
34
+ end,
35
+ }
36
+ rescue StandardError => e
37
+ warn "Error in linkedEditingRange handler: #{e.message}"
38
+ nil
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Server
6
+ module ServerOnTypeFormatting
7
+ private
8
+
9
+ BLOCK_INTRODUCING_KEYWORDS = %w[function async editable const public
10
+ struct enum flags variant union
11
+ interface if elif else while for match unsafe extending defer when].freeze
12
+
13
+ def handle_on_type_formatting(params)
14
+ ch = params["ch"]
15
+ return [] unless ch == "\n"
16
+
17
+ uri = params["textDocument"]["uri"]
18
+ position = params["position"]
19
+ lsp_line = position["line"]
20
+
21
+ content = @workspace.get_content(uri)
22
+ return [] unless content
23
+
24
+ lines = content.split("\n", -1)
25
+ return [] if lsp_line <= 0 || lsp_line >= lines.length
26
+
27
+ prev_idx = lsp_line - 1
28
+ while prev_idx >= 0 && lines[prev_idx].lstrip.empty?
29
+ prev_idx -= 1
30
+ end
31
+ return [] if prev_idx < 0
32
+
33
+ prev_line = lines[prev_idx]
34
+ prev_stripped = prev_line.lstrip
35
+ prev_indent = prev_line.length - prev_stripped.length
36
+
37
+ indent = prev_indent
38
+ if prev_stripped.end_with?(":")
39
+ first_word = prev_stripped.split(/\s+/).first.to_s.delete_suffix(":")
40
+ if BLOCK_INTRODUCING_KEYWORDS.include?(first_word)
41
+ indent = prev_indent + 4
42
+ end
43
+ end
44
+
45
+ below_idx = lsp_line + 1
46
+ while below_idx < lines.length && lines[below_idx].lstrip.empty?
47
+ below_idx += 1
48
+ end
49
+ if below_idx < lines.length
50
+ below_line = lines[below_idx]
51
+ below_stripped = below_line.lstrip
52
+ below_indent = below_line.length - below_stripped.length
53
+ if below_indent < indent && !below_stripped.end_with?(":")
54
+ indent = below_indent
55
+ end
56
+ end
57
+
58
+ current_indent = (lines[lsp_line] || "").length - (lines[lsp_line] || "").lstrip.length
59
+ return [] if indent == current_indent
60
+
61
+ new_text = " " * indent
62
+ [{
63
+ range: {
64
+ start: { line: lsp_line, character: 0 },
65
+ end: { line: lsp_line, character: current_indent },
66
+ },
67
+ newText: new_text,
68
+ }]
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Server
6
+ module ServerProgress
7
+ private
8
+
9
+ PROGRESS_TOKEN_PREFIX = "mt-lsp-progress"
10
+
11
+ def create_progress(title:, message: nil, cancellable: false)
12
+ token = "#{PROGRESS_TOKEN_PREFIX}-#{next_progress_token_id}"
13
+
14
+ @protocol.send_request('window/workDoneProgress/create', { token: token }) do |_result, error|
15
+ warn "[LSP] client rejected progress token #{token}: #{error}" if error
16
+ end
17
+
18
+ begin_value = { kind: 'begin', title: title, cancellable: cancellable }
19
+ begin_value[:message] = message if message
20
+ @protocol.write_notification('$/progress', { token: token, value: begin_value })
21
+ create_progress_handle(@protocol, token)
22
+ end
23
+
24
+ def create_progress_handle(protocol, token)
25
+ handle = Object.new
26
+ handle.define_singleton_method(:report) do |percentage: nil, message: nil|
27
+ value = { kind: 'report' }
28
+ value[:percentage] = percentage if percentage
29
+ value[:message] = message if message
30
+ protocol.write_notification('$/progress', { token: token, value: value })
31
+ end
32
+ handle.define_singleton_method(:done) do |message: nil|
33
+ value = { kind: 'end' }
34
+ value[:message] = message if message
35
+ protocol.write_notification('$/progress', { token: token, value: value })
36
+ end
37
+ handle
38
+ end
39
+
40
+ def next_progress_token_id
41
+ @progress_token_counter ||= 0
42
+ @progress_token_counter += 1
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end