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,575 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module MilkTea
6
+ module LSP
7
+ class Server
8
+ module ServerFormatting
9
+ private
10
+
11
+ def handle_document_symbols(params)
12
+ stages = new_perf_stages
13
+ total_start = stages ? monotonic_time : nil
14
+ uri = params['textDocument']['uri']
15
+ symbols = measure_perf_stage(stages, 'symbols') { @workspace.get_symbols(uri) }
16
+ result = measure_perf_stage(stages, 'format') { symbols.map { |sym| format_document_symbol(sym) } }
17
+
18
+ # Enrich with hierarchical children from AST
19
+ ast = @workspace.get_ast(uri)
20
+ if ast && result
21
+ enrich_with_children(result, ast)
22
+ end
23
+
24
+ module_name = resolve_outline_module_name(uri)
25
+ result = wrap_in_module_hierarchy(result, module_name, uri) if module_name && result&.any?
26
+
27
+ result
28
+ rescue StandardError => e
29
+ warn "Error in documentSymbol handler: #{e.message}"
30
+ []
31
+ ensure
32
+ symbol_count = defined?(result) && result ? result.length : 0
33
+ log_request_stage_breakdown('textDocument/documentSymbol', total_start, uri: uri, stages: stages, summary: "symbols=#{symbol_count}")
34
+ end
35
+
36
+ def resolve_outline_module_name(uri)
37
+ module_name = @workspace.module_name_for_uri(uri) || @workspace.get_facts(uri)&.module_name
38
+ return nil unless module_name && !module_name.empty?
39
+
40
+ segments = module_name.split('.')
41
+ return nil if segments.length <= 1
42
+
43
+ # Only wrap files that live under a real source root: either a
44
+ # package.toml directory or the std/ library hierarchy.
45
+ path = @workspace.send(:uri_to_path, uri)
46
+ if path && !MilkTea::ModuleRoots.package_root_for_path(path) && !path.split('/').include?('std')
47
+ return nil
48
+ end
49
+
50
+ module_name
51
+ end
52
+
53
+ def wrap_in_module_hierarchy(symbols, module_name, uri)
54
+ return symbols unless module_name && !module_name.empty?
55
+
56
+ segments = module_name.split('.')
57
+ return symbols if segments.length <= 1
58
+
59
+ content = @workspace.get_content(uri)
60
+ total_lines = content ? content.count("\n") : 0
61
+ file_range = {
62
+ start: { line: 0, character: 0 },
63
+ end: { line: total_lines, character: 0 },
64
+ }
65
+
66
+ children = symbols
67
+ segments.reverse.each do |seg|
68
+ children = [{
69
+ name: seg,
70
+ kind: 2,
71
+ range: file_range,
72
+ selectionRange: file_range,
73
+ children: children,
74
+ }]
75
+ end
76
+ children
77
+ end
78
+
79
+ def enrich_with_children(symbols, ast)
80
+ removed_local_names = []
81
+ removed_method_names = []
82
+ removed_nested_type_names = []
83
+ removed_event_names = []
84
+ name_index = symbols.each_with_object(Hash.new { |h, k| h[k] = [] }) { |s, h| h[s[:name]] << s }
85
+
86
+ ast.declarations&.each do |decl|
87
+ removed_nested_type_names.concat(collect_nested_type_names(decl)) if decl.is_a?(AST::StructDecl)
88
+
89
+ case decl
90
+ when AST::EventDecl
91
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
92
+ next unless parent
93
+
94
+ parts = ["event[#{decl.capacity}]"]
95
+ parts << "(#{type_detail_string(decl.payload_type)})" if decl.payload_type
96
+ parent[:detail] = parts.join
97
+
98
+ when AST::FunctionDef
99
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
100
+ next unless parent
101
+
102
+ detail_parts = []
103
+ detail_parts << 'const' if decl.respond_to?(:const) && decl.const
104
+ detail_parts << 'async' if decl.respond_to?(:async) && decl.async
105
+ if (ret = type_detail_string(decl.return_type))
106
+ detail_parts << "-> #{ret}"
107
+ end
108
+ parent[:detail] = detail_parts.join(' ') unless detail_parts.empty?
109
+
110
+ locals = collect_local_decls(decl.body)
111
+ next unless locals&.any?
112
+
113
+ parent[:children] ||= []
114
+ parent_children = parent[:children]
115
+ locals.each do |local|
116
+ next unless local.name
117
+
118
+ child = local_decl_symbol(local)
119
+ next unless child
120
+
121
+ parent_children << child unless parent_children.any? { |pc| pc[:name] == child[:name] }
122
+ removed_local_names << local.name
123
+ removed_local_names.concat(descendant_names(child))
124
+ end
125
+ when AST::ExtendingBlock
126
+ type_name_str = decl.type_name.name.parts.join('.')
127
+
128
+ # Find the extending block's own flat token symbol by name + line,
129
+ # enrich it in-place with the implementation detail and methods.
130
+ parent = name_index[type_name_str]&.find { |s| s[:kind] == 23 && symbol_line(s) == (decl.line || 0) }
131
+ unless parent
132
+ line = decl.line || 0
133
+ parent = {
134
+ name: type_name_str,
135
+ kind: 23,
136
+ detail: 'implementation',
137
+ range: { start: { line: line - 1, character: 0 }, end: { line: line, character: 0 } },
138
+ selectionRange: { start: { line: line - 1, character: 0 }, end: { line: line - 1, character: type_name_str.length } },
139
+ children: [],
140
+ }
141
+ symbols << parent
142
+ name_index[type_name_str] << parent
143
+ end
144
+ parent[:detail] = 'implementation'
145
+
146
+ (decl.methods || []).each do |method|
147
+ next unless method.respond_to?(:name) && method.name
148
+
149
+ child = child_method_symbol(method)
150
+ next unless child
151
+
152
+ parent[:children] ||= []
153
+ parent_children = parent[:children]
154
+ parent_children << child unless parent_children.any? { |pc| pc[:name] == child[:name] }
155
+ removed_method_names << child[:name] if child[:kind] == 6
156
+
157
+ locals = collect_local_decls(method.respond_to?(:body) ? method.body : nil)
158
+ next unless locals&.any?
159
+
160
+ child[:children] ||= []
161
+ child_children = child[:children]
162
+ locals.each do |local|
163
+ next unless local.name
164
+
165
+ local_child = local_decl_symbol(local)
166
+ next unless local_child
167
+
168
+ child_children << local_child unless child_children.any? { |pc| pc[:name] == local_child[:name] }
169
+ removed_local_names << local.name
170
+ removed_local_names.concat(descendant_names(local_child))
171
+ end
172
+ end
173
+ when AST::ConstDecl
174
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
175
+ next unless parent
176
+
177
+ if decl.respond_to?(:type) && (detail = type_detail_string(decl.type))
178
+ parent[:detail] = detail
179
+ end
180
+
181
+ next unless decl.block_body
182
+
183
+ locals = collect_local_decls(decl.block_body)
184
+ next unless locals&.any?
185
+
186
+ parent[:children] ||= []
187
+ parent_children = parent[:children]
188
+ locals.each do |local|
189
+ next unless local.name
190
+
191
+ child = local_decl_symbol(local)
192
+ next unless child
193
+
194
+ parent_children << child unless parent_children.any? { |pc| pc[:name] == child[:name] }
195
+ removed_local_names << local.name
196
+ removed_local_names.concat(descendant_names(child))
197
+ end
198
+ else
199
+ parent_name = child_parent_name(decl)
200
+ parent = parent_name ? name_index[parent_name]&.find { |s| symbol_line(s) == (decl.line || 0) } : nil
201
+ next unless parent
202
+
203
+ if decl.is_a?(AST::StructDecl) && decl.implements&.any?
204
+ ifaces = decl.implements.map { |i|
205
+ base = i.respond_to?(:parts) ? i.parts.join('.') : i.name.parts.join('.')
206
+ type_args = i.respond_to?(:type_arguments) ? i.type_arguments : i.respond_to?(:arguments) ? i.arguments : []
207
+ args = if type_args&.any?
208
+ arg_strs = type_args.map { |a| a.respond_to?(:name) ? a.name.parts.join('.') : a.respond_to?(:parts) ? a.parts.join('.') : a.to_s }
209
+ "[#{arg_strs.join(', ')}]"
210
+ else
211
+ ""
212
+ end
213
+ "#{base}#{args}"
214
+ }.join(', ')
215
+ parent[:detail] = "(#{ifaces})"
216
+ end
217
+
218
+ type_params = expand_generic_type_params(decl)
219
+ if type_params&.any?
220
+ parent[:children] ||= []
221
+ parent_children = parent[:children]
222
+ type_params.each do |tp|
223
+ child = type_param_child_symbol(tp)
224
+ next unless child
225
+ parent_children << child unless parent_children.any? { |pc| pc[:name] == child[:name] }
226
+ end
227
+ end
228
+
229
+ children = child_symbols_for(decl)
230
+ next unless children&.any?
231
+
232
+ parent[:children] ||= []
233
+ parent_children = parent[:children]
234
+ children.each do |c|
235
+ next if parent_children.any? { |pc| pc[:name] == c[:name] }
236
+
237
+ parent_children << c
238
+ case c[:kind]
239
+ when 6 then removed_method_names << c[:name]
240
+ when 23 then removed_nested_type_names << c[:name]
241
+ when 24 then removed_event_names << c[:name]
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ if removed_local_names.any?
248
+ removed_set = removed_local_names.to_set
249
+ symbols.reject! { |s| s[:kind] == 13 && removed_set.include?(s[:name]) }
250
+ end
251
+ if removed_method_names.any?
252
+ removed_set = removed_method_names.to_set
253
+ symbols.reject! { |s| (s[:kind] == 6 || s[:kind] == 12) && removed_set.include?(s[:name]) }
254
+ end
255
+ if removed_nested_type_names.any?
256
+ removed_set = removed_nested_type_names.to_set
257
+ symbols.reject! { |s| s[:kind] == 23 && removed_set.include?(s[:name]) && (!s[:children] || s[:children].empty?) }
258
+ end
259
+ if removed_event_names.any?
260
+ removed_set = removed_event_names.to_set
261
+ symbols.reject! { |s| s[:kind] == 24 && removed_set.include?(s[:name]) && (!s[:children] || s[:children].empty?) }
262
+ end
263
+
264
+ symbols
265
+ end
266
+
267
+ def symbol_line(symbol)
268
+ symbol.dig(:range, :start, :line)&.+ 1 || 0
269
+ end
270
+
271
+ def child_parent_name(decl)
272
+ case decl
273
+ when AST::StructDecl then decl.name
274
+ when AST::UnionDecl then decl.name
275
+ when AST::EnumDecl then decl.name
276
+ when AST::FlagsDecl then decl.name
277
+ when AST::VariantDecl then decl.name
278
+ when AST::InterfaceDecl then decl.name
279
+ when AST::OpaqueDecl then decl.name
280
+ when AST::ExtendingBlock then decl.type_name.name.parts.join('.')
281
+ else nil
282
+ end
283
+ end
284
+
285
+ def child_symbols_for(decl)
286
+ case decl
287
+ when AST::StructDecl
288
+ field_children = (decl.fields&.map { |f| child_field_symbol(f) } || []).compact
289
+ nested_children = (decl.nested_types&.map { |n| child_nested_struct_symbol(n) } || []).compact
290
+ event_children = (decl.events&.map { |e| child_event_symbol(e) } || []).compact
291
+ field_children + nested_children + event_children
292
+ when AST::UnionDecl
293
+ (decl.fields&.map { |f| child_field_symbol(f) } || []).compact
294
+ when AST::EnumDecl, AST::FlagsDecl
295
+ (decl.members&.map { |m| child_member_symbol(m, default_line: decl.line) } || []).compact
296
+ when AST::VariantDecl
297
+ (decl.arms&.map { |a| child_variant_arm_symbol(a, default_line: decl.line) } || []).compact
298
+ when AST::InterfaceDecl
299
+ (decl.methods&.map { |m| child_method_symbol(m) } || []).compact
300
+ when AST::ExtendingBlock
301
+ (decl.methods&.map { |m| child_method_symbol(m) } || []).compact
302
+ else nil
303
+ end
304
+ end
305
+
306
+ def child_nested_struct_symbol(nested)
307
+ return nil unless nested.respond_to?(:name) && nested.name && nested.respond_to?(:line) && nested.line
308
+
309
+ grandchildren = child_symbols_for(nested)
310
+ {
311
+ name: nested.name, kind: 23,
312
+ range: { start: { line: nested.line - 1, character: 0 }, end: { line: nested.line, character: 0 } },
313
+ selectionRange: {
314
+ start: { line: nested.line - 1, character: (nested.respond_to?(:column) && nested.column ? nested.column - 1 : 0) },
315
+ end: { line: nested.line - 1, character: (nested.respond_to?(:column) && nested.column ? nested.column - 1 + nested.name.length : 0) },
316
+ },
317
+ }.tap { |s| s[:children] = grandchildren if grandchildren&.any? }
318
+ end
319
+
320
+ def collect_nested_type_names(decl)
321
+ names = []
322
+ decl.nested_types.each do |nested|
323
+ names << nested.name
324
+ names.concat(collect_nested_type_names(nested))
325
+ end
326
+ names
327
+ end
328
+
329
+ def child_field_symbol(f)
330
+ return nil unless f.respond_to?(:name) && f.name && f.respond_to?(:line) && f.line
331
+ detail = f.respond_to?(:type) ? type_detail_string(f.type) : nil
332
+ {
333
+ name: f.name, kind: 8,
334
+ detail: detail,
335
+ range: { start: { line: f.line - 1, character: 0 }, end: { line: f.line, character: 0 } },
336
+ selectionRange: {
337
+ start: { line: f.line - 1, character: (f.respond_to?(:column) && f.column ? f.column - 1 : 0) },
338
+ end: { line: f.line - 1, character: (f.respond_to?(:column) && f.column ? f.column - 1 + f.name.length : 0) },
339
+ },
340
+ }.compact
341
+ end
342
+
343
+ def child_event_symbol(e)
344
+ return nil unless e.respond_to?(:name) && e.name && e.respond_to?(:line) && e.line
345
+
346
+ parts = ["event[#{e.capacity}]"]
347
+ parts << "(#{type_detail_string(e.payload_type)})" if e.respond_to?(:payload_type) && e.payload_type
348
+
349
+ {
350
+ name: e.name, kind: 24,
351
+ detail: parts.join,
352
+ range: { start: { line: e.line - 1, character: 0 }, end: { line: e.line, character: 0 } },
353
+ selectionRange: {
354
+ start: { line: e.line - 1, character: (e.respond_to?(:column) && e.column ? e.column - 1 : 0) },
355
+ end: { line: e.line - 1, character: (e.respond_to?(:column) && e.column ? e.column - 1 + e.name.length : 0) },
356
+ },
357
+ }
358
+ end
359
+
360
+ def child_member_symbol(m, default_line: nil)
361
+ line = (m.respond_to?(:line) && m.line) ? m.line : default_line
362
+ return nil unless m.respond_to?(:name) && m.name && line
363
+
364
+ col = m.respond_to?(:column) && m.column ? m.column : 1
365
+ {
366
+ name: m.name, kind: 22,
367
+ range: { start: { line: line - 1, character: 0 }, end: { line: line, character: 0 } },
368
+ selectionRange: {
369
+ start: { line: line - 1, character: col - 1 },
370
+ end: { line: line - 1, character: col - 1 + m.name.length },
371
+ },
372
+ }
373
+ end
374
+
375
+ def child_variant_arm_symbol(a, default_line: nil)
376
+ line = (a.respond_to?(:line) && a.line) ? a.line : default_line
377
+ return nil unless a.respond_to?(:name) && a.name && line
378
+ col = a.respond_to?(:column) && a.column ? a.column : 1
379
+ {
380
+ name: a.name, kind: 22,
381
+ range: { start: { line: line - 1, character: 0 }, end: { line: line, character: 0 } },
382
+ selectionRange: {
383
+ start: { line: line - 1, character: col - 1 },
384
+ end: { line: line - 1, character: col - 1 + a.name.length },
385
+ },
386
+ }
387
+ end
388
+
389
+ def collect_local_decls(body)
390
+ return [] unless body
391
+
392
+ case body
393
+ when Array
394
+ body.flat_map { |stmt| collect_local_decls(stmt) }.compact
395
+ when AST::LocalDecl
396
+ [body]
397
+ when AST::IfStmt
398
+ (body.branches || []).flat_map { |b| collect_local_decls(b.body) } +
399
+ collect_local_decls(body.else_body)
400
+ when AST::WhileStmt
401
+ collect_local_decls(body.body)
402
+ when AST::ForStmt
403
+ collect_local_decls(body.body) + body.bindings
404
+ when AST::MatchStmt
405
+ (body.arms || []).flat_map { |arm| collect_local_decls(arm.body) }
406
+ when AST::ParallelBlockStmt
407
+ (body.bodies || []).flat_map { |b| collect_local_decls(b) }
408
+ when AST::DeferStmt
409
+ collect_local_decls(body.body)
410
+ when AST::UnsafeStmt
411
+ collect_local_decls(body.body)
412
+ when AST::WhenStmt
413
+ (body.branches || []).flat_map { |b| collect_local_decls(b.body) } +
414
+ collect_local_decls(body.else_body)
415
+ when AST::ErrorBlockStmt
416
+ collect_local_decls(body.body)
417
+ else
418
+ []
419
+ end
420
+ end
421
+
422
+ def local_decl_symbol(decl)
423
+ return nil unless decl.name
424
+ return nil if decl.name == '_'
425
+
426
+ line = decl.line || 0
427
+ col = decl.column || 1
428
+ detail = decl.respond_to?(:type) ? type_detail_string(decl.type) : nil
429
+
430
+ children = []
431
+ if decl.respond_to?(:value) && decl.value.is_a?(AST::ProcExpr)
432
+ detail ||= proc_signature_detail(decl.value)
433
+ proc_locals = collect_local_decls(decl.value.body)
434
+ children = proc_locals.filter_map { |l| local_decl_symbol(l) }
435
+ end
436
+
437
+ {
438
+ name: decl.name, kind: 13,
439
+ detail: detail,
440
+ range: { start: { line: line - 1, character: col - 1 }, end: { line: line - 1, character: col - 1 + decl.name.length } },
441
+ selectionRange: { start: { line: line - 1, character: col - 1 }, end: { line: line - 1, character: col - 1 + decl.name.length } },
442
+ children: children.any? ? children : nil,
443
+ }.compact
444
+ end
445
+
446
+ def descendant_names(symbol)
447
+ return [] unless symbol[:children]
448
+
449
+ symbol[:children].flat_map { |c| [c[:name]] + descendant_names(c) }
450
+ end
451
+
452
+ def expand_generic_type_params(decl)
453
+ return [] unless decl.respond_to?(:type_params) && decl.type_params
454
+
455
+ decl.type_params.flat_map do |tp|
456
+ if tp.respond_to?(:type_params) && tp.type_params&.any?
457
+ expand_generic_type_params(tp)
458
+ else
459
+ [tp]
460
+ end
461
+ end
462
+ end
463
+
464
+ def type_param_child_symbol(tp)
465
+ return nil unless tp.respond_to?(:name) && tp.name
466
+ return nil unless tp.respond_to?(:line) && tp.line
467
+
468
+ col = tp.respond_to?(:column) && tp.column ? tp.column : 1
469
+ {
470
+ name: tp.name, kind: 26,
471
+ range: { start: { line: tp.line - 1, character: col - 1 }, end: { line: tp.line - 1, character: col - 1 + tp.name.length } },
472
+ selectionRange: {
473
+ start: { line: tp.line - 1, character: col - 1 },
474
+ end: { line: tp.line - 1, character: col - 1 + tp.name.length },
475
+ },
476
+ }
477
+ end
478
+
479
+ def proc_signature_detail(proc_expr)
480
+ params = proc_expr.params&.map { |p| "#{p.name}: #{type_detail_string(p.type)}" }&.join(', ') || ''
481
+ ret = type_detail_string(proc_expr.return_type) || 'void'
482
+ "proc(#{params}) -> #{ret}"
483
+ end
484
+
485
+ def child_method_symbol(m)
486
+ return nil unless m.respond_to?(:name) && m.name && m.respond_to?(:line) && m.line
487
+
488
+ detail_parts = []
489
+ detail_parts << 'mut' if m.respond_to?(:kind) && m.kind == :editable_function
490
+ detail_parts << 'static' if m.respond_to?(:kind) && m.kind == :static_function
491
+ detail_parts << 'async' if m.respond_to?(:async) && m.async
492
+ if (ret = type_detail_string(m.return_type))
493
+ detail_parts << "-> #{ret}"
494
+ end
495
+ {
496
+ name: m.name, kind: 6,
497
+ range: { start: { line: m.line - 1, character: 0 }, end: { line: (m.respond_to?(:end_line) && m.end_line ? m.end_line : m.line), character: 0 } },
498
+ selectionRange: {
499
+ start: { line: m.line - 1, character: (m.respond_to?(:column) && m.column ? m.column - 1 : 0) },
500
+ end: { line: m.line - 1, character: (m.respond_to?(:column) && m.column ? m.column - 1 + m.name.length : 0) },
501
+ },
502
+ detail: detail_parts.empty? ? nil : detail_parts.join(' '),
503
+ }.compact
504
+ end
505
+
506
+ def type_detail_string(type)
507
+ return nil unless type
508
+
509
+ case type
510
+ when AST::TypeRef
511
+ type.to_s
512
+ when AST::ProcType
513
+ params = (type.params || []).map { |p| type_detail_string(p.type) }.join(', ')
514
+ ret = type_detail_string(type.return_type) || 'void'
515
+ "proc(#{params}) -> #{ret}"
516
+ when AST::TupleType
517
+ base = "(#{(type.element_types || []).map { |t| type_detail_string(t) }.join(', ')})"
518
+ type.nullable ? "#{base}?" : base
519
+ when AST::DynType
520
+ "dyn[#{type.interface}]"
521
+ end
522
+ end
523
+
524
+ def handle_formatting(params)
525
+ uri = params['textDocument']['uri']
526
+ content = @workspace.get_content(uri)
527
+
528
+ formatted = Formatter.format_source(content, path: uri, mode: @format_mode)
529
+ line_count = content.count("\n")
530
+
531
+ [
532
+ {
533
+ range: {
534
+ start: { line: 0, character: 0 },
535
+ end: { line: line_count + 1, character: 0 }
536
+ },
537
+ newText: formatted
538
+ }
539
+ ]
540
+ rescue StandardError => e
541
+ warn "Error in formatting handler: #{e.message}"
542
+ []
543
+ end
544
+
545
+ def handle_range_formatting(params)
546
+ uri = params['textDocument']['uri']
547
+ content = @workspace.get_content(uri)
548
+ range = params['range'] || {}
549
+ start_pos = range['start'] || { 'line' => 0, 'character' => 0 }
550
+ end_pos = range['end'] || { 'line' => 0, 'character' => 0 }
551
+
552
+ start_off = @workspace.position_to_offset(uri, start_pos['line'], start_pos['character'])
553
+ end_off = @workspace.position_to_offset(uri, end_pos['line'], end_pos['character'])
554
+ return [] if end_off < start_off
555
+
556
+ segment = content.byteslice(start_off...end_off).to_s
557
+ formatted_segment = Formatter.format_source(segment, path: uri, mode: @format_mode)
558
+
559
+ [
560
+ {
561
+ range: {
562
+ start: { line: start_pos['line'], character: start_pos['character'] },
563
+ end: { line: end_pos['line'], character: end_pos['character'] }
564
+ },
565
+ newText: formatted_segment
566
+ }
567
+ ]
568
+ rescue StandardError => e
569
+ warn "Error in rangeFormatting handler: #{e.message}"
570
+ []
571
+ end
572
+ end
573
+ end
574
+ end
575
+ end