mt-lang 0.3.9 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. checksums.yaml +4 -4
  2. data/lib/milk_tea/base.rb +1 -2
  3. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +0 -2
  4. data/lib/milk_tea/bindings/bindgen/declaration.rb +0 -2
  5. data/lib/milk_tea/bindings/bindgen/emitter.rb +0 -2
  6. data/lib/milk_tea/bindings/bindgen/overrides.rb +0 -2
  7. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +51 -53
  8. data/lib/milk_tea/bindings/imported_bindings/generator.rb +7 -9
  9. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +0 -2
  10. data/lib/milk_tea/bindings/imported_bindings/naming.rb +6 -8
  11. data/lib/milk_tea/bindings/imported_bindings.rb +0 -2
  12. data/lib/milk_tea/bindings/opengl_registry.rb +3 -7
  13. data/lib/milk_tea/bindings/steamworks.rb +12 -16
  14. data/lib/milk_tea/bindings/upstream_sources.rb +10 -14
  15. data/lib/milk_tea/bindings/vendored_box2d.rb +10 -12
  16. data/lib/milk_tea/bindings/vendored_cjson.rb +6 -8
  17. data/lib/milk_tea/bindings/vendored_flecs.rb +10 -12
  18. data/lib/milk_tea/bindings/vendored_glfw.rb +11 -13
  19. data/lib/milk_tea/bindings/vendored_libuv.rb +11 -13
  20. data/lib/milk_tea/bindings/vendored_pcre2.rb +11 -13
  21. data/lib/milk_tea/bindings/vendored_raylib.rb +6 -10
  22. data/lib/milk_tea/bindings/vendored_sdl3.rb +11 -13
  23. data/lib/milk_tea/bindings/vendored_steamworks.rb +1 -5
  24. data/lib/milk_tea/core/ast.rb +103 -45
  25. data/lib/milk_tea/core/async_runtime_installer.rb +0 -2
  26. data/lib/milk_tea/core/bindings/attribute_binding.rb +1 -3
  27. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +147 -84
  28. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +394 -396
  29. data/lib/milk_tea/core/c_backend/expressions.rb +489 -476
  30. data/lib/milk_tea/core/c_backend/feature_detection.rb +446 -437
  31. data/lib/milk_tea/core/c_backend/format_helpers.rb +0 -2
  32. data/lib/milk_tea/core/c_backend/reachability.rb +339 -341
  33. data/lib/milk_tea/core/c_backend/reinterpret.rb +231 -217
  34. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +531 -526
  35. data/lib/milk_tea/core/c_backend/statements.rb +528 -530
  36. data/lib/milk_tea/core/c_backend/type_collectors.rb +1344 -1346
  37. data/lib/milk_tea/core/c_backend/type_declaration.rb +215 -213
  38. data/lib/milk_tea/core/c_backend/type_system.rb +347 -349
  39. data/lib/milk_tea/core/c_backend.rb +17 -9
  40. data/lib/milk_tea/core/compatibility_helpers.rb +0 -2
  41. data/lib/milk_tea/core/compile_time/const_eval.rb +7 -12
  42. data/lib/milk_tea/core/compile_time.rb +32 -38
  43. data/lib/milk_tea/core/control_flow/builder.rb +3 -5
  44. data/lib/milk_tea/core/control_flow/constant_propagation.rb +14 -17
  45. data/lib/milk_tea/core/control_flow/definite_assignment.rb +0 -1
  46. data/lib/milk_tea/core/ir.rb +10 -10
  47. data/lib/milk_tea/core/lexer/character_classes.rb +5 -7
  48. data/lib/milk_tea/core/lexer/format_strings.rb +7 -9
  49. data/lib/milk_tea/core/lexer/heredocs.rb +22 -22
  50. data/lib/milk_tea/core/lexer/indentation.rb +0 -2
  51. data/lib/milk_tea/core/lexer/numbers.rb +0 -2
  52. data/lib/milk_tea/core/lexer/recovery.rb +0 -2
  53. data/lib/milk_tea/core/lexer/strings.rb +0 -2
  54. data/lib/milk_tea/core/lexer/symbols.rb +0 -2
  55. data/lib/milk_tea/core/lexer/trivia.rb +0 -2
  56. data/lib/milk_tea/core/lexer.rb +0 -2
  57. data/lib/milk_tea/core/lowering/async/analysis.rb +0 -2
  58. data/lib/milk_tea/core/lowering/async/lowering.rb +1264 -1265
  59. data/lib/milk_tea/core/lowering/async/normalization.rb +0 -2
  60. data/lib/milk_tea/core/lowering/async.rb +627 -630
  61. data/lib/milk_tea/core/lowering/block.rb +413 -416
  62. data/lib/milk_tea/core/lowering/calls.rb +1381 -1384
  63. data/lib/milk_tea/core/lowering/declarations.rb +180 -183
  64. data/lib/milk_tea/core/lowering/dyn.rb +48 -50
  65. data/lib/milk_tea/core/lowering/events.rb +983 -985
  66. data/lib/milk_tea/core/lowering/expressions.rb +1513 -1518
  67. data/lib/milk_tea/core/lowering/foreign_cstr.rb +181 -184
  68. data/lib/milk_tea/core/lowering/functions.rb +185 -188
  69. data/lib/milk_tea/core/lowering/loops.rb +1028 -1031
  70. data/lib/milk_tea/core/lowering/proc.rb +363 -366
  71. data/lib/milk_tea/core/lowering/resolve.rb +2133 -2135
  72. data/lib/milk_tea/core/lowering/scans.rb +179 -181
  73. data/lib/milk_tea/core/lowering/str_buffer.rb +102 -105
  74. data/lib/milk_tea/core/lowering/utils.rb +1040 -1044
  75. data/lib/milk_tea/core/lowering.rb +5 -7
  76. data/lib/milk_tea/core/module_binder.rb +0 -2
  77. data/lib/milk_tea/core/module_loader/errors.rb +4 -2
  78. data/lib/milk_tea/core/module_loader.rb +113 -20
  79. data/lib/milk_tea/core/module_path_resolver.rb +0 -2
  80. data/lib/milk_tea/core/module_roots.rb +6 -8
  81. data/lib/milk_tea/core/parser/attributes.rb +0 -2
  82. data/lib/milk_tea/core/parser/blocks.rb +0 -2
  83. data/lib/milk_tea/core/parser/declarations.rb +0 -2
  84. data/lib/milk_tea/core/parser/expressions.rb +8 -9
  85. data/lib/milk_tea/core/parser/recovery.rb +0 -2
  86. data/lib/milk_tea/core/parser/statements.rb +0 -2
  87. data/lib/milk_tea/core/parser/types.rb +8 -10
  88. data/lib/milk_tea/core/parser.rb +0 -2
  89. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +31 -33
  90. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +0 -2
  91. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +0 -2
  92. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +0 -2
  93. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +14 -16
  94. data/lib/milk_tea/core/semantic_analyzer/calls.rb +0 -2
  95. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1 -14
  96. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +18 -21
  97. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +0 -3
  98. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +12 -15
  99. data/lib/milk_tea/core/semantic_analyzer/generics.rb +57 -59
  100. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +0 -2
  101. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +5 -9
  102. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +0 -3
  103. data/lib/milk_tea/core/semantic_analyzer/statements.rb +35 -40
  104. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +10 -12
  105. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +0 -6
  106. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +102 -105
  107. data/lib/milk_tea/core/semantic_analyzer.rb +17 -8
  108. data/lib/milk_tea/core/types/layout.rb +21 -22
  109. data/lib/milk_tea/core/types/predicates.rb +0 -2
  110. data/lib/milk_tea/core/types/registry.rb +16 -18
  111. data/lib/milk_tea/core/types/visitor.rb +0 -4
  112. data/lib/milk_tea/dap/backends/lldb_dap.rb +0 -2
  113. data/lib/milk_tea/dap/protocol.rb +1 -1
  114. data/lib/milk_tea/dap/server/breakpoints.rb +0 -2
  115. data/lib/milk_tea/dap/server/debug_map.rb +0 -2
  116. data/lib/milk_tea/dap/server/handlers.rb +0 -2
  117. data/lib/milk_tea/dap/server/launch.rb +0 -2
  118. data/lib/milk_tea/dap/server/lldb_backend.rb +8 -10
  119. data/lib/milk_tea/dap/server/pause_diagnostics.rb +14 -16
  120. data/lib/milk_tea/dap/server/utilities.rb +0 -2
  121. data/lib/milk_tea/dap/server/wire.rb +0 -2
  122. data/lib/milk_tea/dap/server.rb +0 -2
  123. data/lib/milk_tea/lsp/diagnostics.rb +16 -16
  124. data/lib/milk_tea/lsp/server/call_hierarchy.rb +11 -12
  125. data/lib/milk_tea/lsp/server/code_actions.rb +355 -357
  126. data/lib/milk_tea/lsp/server/code_lens.rb +23 -25
  127. data/lib/milk_tea/lsp/server/completion.rb +873 -875
  128. data/lib/milk_tea/lsp/server/configuration.rb +129 -131
  129. data/lib/milk_tea/lsp/server/debug_info.rb +0 -2
  130. data/lib/milk_tea/lsp/server/definition.rb +564 -565
  131. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +191 -193
  132. data/lib/milk_tea/lsp/server/execute_command.rb +0 -2
  133. data/lib/milk_tea/lsp/server/folding_range.rb +0 -4
  134. data/lib/milk_tea/lsp/server/formatting.rb +478 -480
  135. data/lib/milk_tea/lsp/server/hover.rb +1282 -1284
  136. data/lib/milk_tea/lsp/server/inlay_hints.rb +179 -181
  137. data/lib/milk_tea/lsp/server/lifecycle.rb +192 -194
  138. data/lib/milk_tea/lsp/server/linked_editing_range.rb +0 -2
  139. data/lib/milk_tea/lsp/server/on_type_formatting.rb +0 -2
  140. data/lib/milk_tea/lsp/server/progress.rb +0 -2
  141. data/lib/milk_tea/lsp/server/references.rb +316 -318
  142. data/lib/milk_tea/lsp/server/rename.rb +445 -447
  143. data/lib/milk_tea/lsp/server/selection_range.rb +0 -4
  144. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1318 -1320
  145. data/lib/milk_tea/lsp/server/signature_help.rb +146 -148
  146. data/lib/milk_tea/lsp/server/text_documents.rb +99 -101
  147. data/lib/milk_tea/lsp/server/type_hierarchy.rb +2 -4
  148. data/lib/milk_tea/lsp/server/utilities.rb +397 -399
  149. data/lib/milk_tea/lsp/server.rb +0 -3
  150. data/lib/milk_tea/lsp/workspace/analysis.rb +40 -42
  151. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +4 -10
  152. data/lib/milk_tea/lsp/workspace/utilities.rb +19 -19
  153. data/lib/milk_tea/packages/atomic_write.rb +4 -5
  154. data/lib/milk_tea/packages/dependency_solver.rb +15 -15
  155. data/lib/milk_tea/packages/lock.rb +4 -4
  156. data/lib/milk_tea/packages/manager_cli.rb +1 -1
  157. data/lib/milk_tea/packages/manifest.rb +3 -3
  158. data/lib/milk_tea/packages/registry_store.rb +4 -6
  159. data/lib/milk_tea/packages/services.rb +16 -16
  160. data/lib/milk_tea/packages/source_cache.rb +4 -6
  161. data/lib/milk_tea/packages/source_resolver.rb +44 -44
  162. data/lib/milk_tea/packages/version.rb +8 -8
  163. data/lib/milk_tea/tooling/build.rb +3 -7
  164. data/lib/milk_tea/tooling/build_cache.rb +0 -2
  165. data/lib/milk_tea/tooling/debug_info_formatter.rb +31 -33
  166. data/lib/milk_tea/tooling/debug_map.rb +8 -10
  167. data/lib/milk_tea/tooling/docs_app.rb +0 -2
  168. data/lib/milk_tea/tooling/linter/doc_tags.rb +25 -27
  169. data/lib/milk_tea/tooling/linter/fix_engine.rb +87 -24
  170. data/lib/milk_tea/tooling/linter/flow_rules.rb +44 -46
  171. data/lib/milk_tea/tooling/linter/imports_platform.rb +14 -16
  172. data/lib/milk_tea/tooling/linter/release_rules.rb +3 -5
  173. data/lib/milk_tea/tooling/linter/reserved_names.rb +23 -25
  174. data/lib/milk_tea/tooling/linter/rules.rb +87 -89
  175. data/lib/milk_tea/tooling/linter/source_helpers.rb +40 -42
  176. data/lib/milk_tea/tooling/linter/trailing_comma.rb +17 -19
  177. data/lib/milk_tea/tooling/linter/visitors.rb +112 -54
  178. data/lib/milk_tea/tooling/linter.rb +0 -2
  179. data/lib/milk_tea/tooling/project_scaffold.rb +0 -2
  180. data/lib/milk_tea/tooling/run.rb +0 -2
  181. data/lib/milk_tea/tooling/toolchain_cli.rb +0 -2
  182. data/std/cli.mt +13 -32
  183. data/std/color.mt +28 -28
  184. data/std/encoding.mt +4 -4
  185. data/std/graph.mt +1 -2
  186. data/std/hash.mt +14 -14
  187. data/std/http/server.mt +3 -3
  188. data/std/input.mt +1 -1
  189. data/std/json.mt +1 -1
  190. data/std/linear_algebra.mt +6 -6
  191. data/std/net/mux.mt +1 -1
  192. data/std/noise.mt +23 -23
  193. data/std/parse.mt +2 -2
  194. data/std/process.mt +3 -4
  195. data/std/sparse_set.mt +1 -1
  196. data/std/steering.mt +5 -5
  197. data/std/str.mt +1 -1
  198. data/std/tween.mt +4 -4
  199. data/std/utility.mt +3 -3
  200. metadata +2 -2
@@ -6,569 +6,567 @@ module MilkTea
6
6
  module LSP
7
7
  class Server
8
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?
9
+ def handle_document_symbols(params)
10
+ stages = new_perf_stages
11
+ total_start = stages ? monotonic_time : nil
12
+ uri = params['textDocument']['uri']
13
+ symbols = measure_perf_stage(stages, 'symbols') { @workspace.get_symbols(uri) }
14
+ result = measure_perf_stage(stages, 'format') { symbols.map { |sym| format_document_symbol(sym) } }
15
+
16
+ # Enrich with hierarchical children from AST
17
+ ast = @workspace.get_ast(uri)
18
+ if ast && result
19
+ enrich_with_children(result, ast)
20
+ end
39
21
 
40
- segments = module_name.split('.')
41
- return nil if segments.length <= 1
22
+ module_name = resolve_outline_module_name(uri)
23
+ result = wrap_in_module_hierarchy(result, module_name, uri) if module_name && result&.any?
42
24
 
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
25
+ result
26
+ rescue StandardError => e
27
+ warn "Error in documentSymbol handler: #{e.message}"
28
+ []
29
+ ensure
30
+ symbol_count = defined?(result) && result ? result.length : 0
31
+ log_request_stage_breakdown('textDocument/documentSymbol', total_start, uri: uri, stages: stages, summary: "symbols=#{symbol_count}")
48
32
  end
49
33
 
50
- module_name
51
- end
34
+ def resolve_outline_module_name(uri)
35
+ module_name = @workspace.module_name_for_uri(uri) || @workspace.get_facts(uri)&.module_name
36
+ return nil unless module_name && !module_name.empty?
52
37
 
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 }
38
+ segments = module_name.split('.')
39
+ return nil if segments.length <= 1
85
40
 
86
- ast.declarations&.each do |decl|
87
- removed_nested_type_names.concat(collect_nested_type_names(decl)) if decl.is_a?(AST::StructDecl)
41
+ # Only wrap files that live under a real source root: either a
42
+ # package.toml directory or the std/ library hierarchy.
43
+ path = @workspace.send(:uri_to_path, uri)
44
+ if path && !MilkTea::ModuleRoots.package_root_for_path(path) && !path.split('/').include?('std')
45
+ return nil
46
+ end
88
47
 
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?
48
+ module_name
49
+ end
109
50
 
110
- locals = collect_local_decls(decl.body)
111
- next unless locals&.any?
51
+ def wrap_in_module_hierarchy(symbols, module_name, uri)
52
+ return symbols unless module_name && !module_name.empty?
112
53
 
113
- parent[:children] ||= []
114
- parent_children = parent[:children]
115
- locals.each do |local|
116
- next unless local.name
54
+ segments = module_name.split('.')
55
+ return symbols if segments.length <= 1
117
56
 
118
- child = local_decl_symbol(local)
119
- next unless child
57
+ content = @workspace.get_content(uri)
58
+ total_lines = content ? content.count("\n") : 0
59
+ file_range = {
60
+ start: { line: 0, character: 0 },
61
+ end: { line: total_lines, character: 0 },
62
+ }
120
63
 
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'
64
+ children = symbols
65
+ segments.reverse.each do |seg|
66
+ children = [{
67
+ name: seg,
68
+ kind: 2,
69
+ range: file_range,
70
+ selectionRange: file_range,
71
+ children: children,
72
+ }]
73
+ end
74
+ children
75
+ end
145
76
 
146
- (decl.methods || []).each do |method|
147
- next unless method.respond_to?(:name) && method.name
77
+ def enrich_with_children(symbols, ast)
78
+ removed_local_names = []
79
+ removed_method_names = []
80
+ removed_nested_type_names = []
81
+ removed_event_names = []
82
+ name_index = symbols.each_with_object(Hash.new { |h, k| h[k] = [] }) { |s, h| h[s[:name]] << s }
83
+
84
+ ast.declarations&.each do |decl|
85
+ removed_nested_type_names.concat(collect_nested_type_names(decl)) if decl.is_a?(AST::StructDecl)
86
+
87
+ case decl
88
+ when AST::EventDecl
89
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
90
+ next unless parent
91
+
92
+ parts = ["event[#{decl.capacity}]"]
93
+ parts << "(#{type_detail_string(decl.payload_type)})" if decl.payload_type
94
+ parent[:detail] = parts.join
95
+
96
+ when AST::FunctionDef
97
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
98
+ next unless parent
99
+
100
+ detail_parts = []
101
+ detail_parts << 'const' if decl.respond_to?(:const) && decl.const
102
+ detail_parts << 'async' if decl.respond_to?(:async) && decl.async
103
+ if (ret = type_detail_string(decl.return_type))
104
+ detail_parts << "-> #{ret}"
105
+ end
106
+ parent[:detail] = detail_parts.join(' ') unless detail_parts.empty?
148
107
 
149
- child = child_method_symbol(method)
150
- next unless child
108
+ locals = collect_local_decls(decl.body)
109
+ next unless locals&.any?
151
110
 
152
111
  parent[:children] ||= []
153
112
  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
113
  locals.each do |local|
163
114
  next unless local.name
164
115
 
165
- local_child = local_decl_symbol(local)
166
- next unless local_child
116
+ child = local_decl_symbol(local)
117
+ next unless child
167
118
 
168
- child_children << local_child unless child_children.any? { |pc| pc[:name] == local_child[:name] }
119
+ parent_children << child unless parent_children.any? { |pc| pc[:name] == child[:name] }
169
120
  removed_local_names << local.name
170
- removed_local_names.concat(descendant_names(local_child))
121
+ removed_local_names.concat(descendant_names(child))
171
122
  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
123
+ when AST::ExtendingBlock
124
+ type_name_str = decl.type_name.name.parts.join('.')
125
+
126
+ # Find the extending block's own flat token symbol by name + line,
127
+ # enrich it in-place with the implementation detail and methods.
128
+ parent = name_index[type_name_str]&.find { |s| s[:kind] == 23 && symbol_line(s) == (decl.line || 0) }
129
+ unless parent
130
+ line = decl.line || 0
131
+ parent = {
132
+ name: type_name_str,
133
+ kind: 23,
134
+ detail: 'implementation',
135
+ range: { start: { line: line - 1, character: 0 }, end: { line: line, character: 0 } },
136
+ selectionRange: { start: { line: line - 1, character: 0 }, end: { line: line - 1, character: type_name_str.length } },
137
+ children: [],
138
+ }
139
+ symbols << parent
140
+ name_index[type_name_str] << parent
141
+ end
142
+ parent[:detail] = 'implementation'
176
143
 
177
- if decl.respond_to?(:type) && (detail = type_detail_string(decl.type))
178
- parent[:detail] = detail
179
- end
144
+ (decl.methods || []).each do |method|
145
+ next unless method.respond_to?(:name) && method.name
180
146
 
181
- next unless decl.block_body
147
+ child = child_method_symbol(method)
148
+ next unless child
182
149
 
183
- locals = collect_local_decls(decl.block_body)
184
- next unless locals&.any?
150
+ parent[:children] ||= []
151
+ parent_children = parent[:children]
152
+ parent_children << child unless parent_children.any? { |pc| pc[:name] == child[:name] }
153
+ removed_method_names << child[:name] if child[:kind] == 6
185
154
 
186
- parent[:children] ||= []
187
- parent_children = parent[:children]
188
- locals.each do |local|
189
- next unless local.name
155
+ locals = collect_local_decls(method.respond_to?(:body) ? method.body : nil)
156
+ next unless locals&.any?
190
157
 
191
- child = local_decl_symbol(local)
192
- next unless child
158
+ child[:children] ||= []
159
+ child_children = child[:children]
160
+ locals.each do |local|
161
+ next unless local.name
193
162
 
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
163
+ local_child = local_decl_symbol(local)
164
+ next unless local_child
165
+
166
+ child_children << local_child unless child_children.any? { |pc| pc[:name] == local_child[:name] }
167
+ removed_local_names << local.name
168
+ removed_local_names.concat(descendant_names(local_child))
169
+ end
170
+ end
171
+ when AST::ConstDecl
172
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
173
+ next unless parent
174
+
175
+ if decl.respond_to?(:type) && (detail = type_detail_string(decl.type))
176
+ parent[:detail] = detail
177
+ end
178
+
179
+ next unless decl.block_body
180
+
181
+ locals = collect_local_decls(decl.block_body)
182
+ next unless locals&.any?
217
183
 
218
- type_params = expand_generic_type_params(decl)
219
- if type_params&.any?
220
184
  parent[:children] ||= []
221
185
  parent_children = parent[:children]
222
- type_params.each do |tp|
223
- child = type_param_child_symbol(tp)
186
+ locals.each do |local|
187
+ next unless local.name
188
+
189
+ child = local_decl_symbol(local)
224
190
  next unless child
191
+
225
192
  parent_children << child unless parent_children.any? { |pc| pc[:name] == child[:name] }
193
+ removed_local_names << local.name
194
+ removed_local_names.concat(descendant_names(child))
195
+ end
196
+ else
197
+ parent_name = child_parent_name(decl)
198
+ parent = parent_name ? name_index[parent_name]&.find { |s| symbol_line(s) == (decl.line || 0) } : nil
199
+ next unless parent
200
+
201
+ if decl.is_a?(AST::StructDecl) && decl.implements&.any?
202
+ ifaces = decl.implements.map { |i|
203
+ base = i.respond_to?(:parts) ? i.parts.join('.') : i.name.parts.join('.')
204
+ type_args = i.respond_to?(:type_arguments) ? i.type_arguments : i.respond_to?(:arguments) ? i.arguments : []
205
+ args = if type_args&.any?
206
+ arg_strs = type_args.map { |a| a.respond_to?(:name) ? a.name.parts.join('.') : a.respond_to?(:parts) ? a.parts.join('.') : a.to_s }
207
+ "[#{arg_strs.join(', ')}]"
208
+ else
209
+ ""
210
+ end
211
+ "#{base}#{args}"
212
+ }.join(', ')
213
+ parent[:detail] = "(#{ifaces})"
226
214
  end
227
- end
228
215
 
229
- children = child_symbols_for(decl)
230
- next unless children&.any?
216
+ type_params = expand_generic_type_params(decl)
217
+ if type_params&.any?
218
+ parent[:children] ||= []
219
+ parent_children = parent[:children]
220
+ type_params.each do |tp|
221
+ child = type_param_child_symbol(tp)
222
+ next unless child
223
+ parent_children << child unless parent_children.any? { |pc| pc[:name] == child[:name] }
224
+ end
225
+ end
231
226
 
232
- parent[:children] ||= []
233
- parent_children = parent[:children]
234
- children.each do |c|
235
- next if parent_children.any? { |pc| pc[:name] == c[:name] }
227
+ children = child_symbols_for(decl)
228
+ next unless children&.any?
236
229
 
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]
230
+ parent[:children] ||= []
231
+ parent_children = parent[:children]
232
+ children.each do |c|
233
+ next if parent_children.any? { |pc| pc[:name] == c[:name] }
234
+
235
+ parent_children << c
236
+ case c[:kind]
237
+ when 6 then removed_method_names << c[:name]
238
+ when 23 then removed_nested_type_names << c[:name]
239
+ when 24 then removed_event_names << c[:name]
240
+ end
242
241
  end
243
242
  end
244
243
  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
244
 
264
- symbols
265
- end
266
-
267
- def symbol_line(symbol)
268
- symbol.dig(:range, :start, :line)&.+ 1 || 0
269
- end
245
+ if removed_local_names.any?
246
+ removed_set = removed_local_names.to_set
247
+ symbols.reject! { |s| s[:kind] == 13 && removed_set.include?(s[:name]) }
248
+ end
249
+ if removed_method_names.any?
250
+ removed_set = removed_method_names.to_set
251
+ symbols.reject! { |s| (s[:kind] == 6 || s[:kind] == 12) && removed_set.include?(s[:name]) }
252
+ end
253
+ if removed_nested_type_names.any?
254
+ removed_set = removed_nested_type_names.to_set
255
+ symbols.reject! { |s| s[:kind] == 23 && removed_set.include?(s[:name]) && (!s[:children] || s[:children].empty?) }
256
+ end
257
+ if removed_event_names.any?
258
+ removed_set = removed_event_names.to_set
259
+ symbols.reject! { |s| s[:kind] == 24 && removed_set.include?(s[:name]) && (!s[:children] || s[:children].empty?) }
260
+ end
270
261
 
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
262
+ symbols
282
263
  end
283
- end
284
264
 
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
265
+ def symbol_line(symbol)
266
+ symbol.dig(:range, :start, :line)&.+ 1 || 0
303
267
  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
268
 
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))
269
+ def child_parent_name(decl)
270
+ case decl
271
+ when AST::StructDecl then decl.name
272
+ when AST::UnionDecl then decl.name
273
+ when AST::EnumDecl then decl.name
274
+ when AST::FlagsDecl then decl.name
275
+ when AST::VariantDecl then decl.name
276
+ when AST::InterfaceDecl then decl.name
277
+ when AST::OpaqueDecl then decl.name
278
+ when AST::ExtendingBlock then decl.type_name.name.parts.join('.')
279
+ else nil
280
+ end
325
281
  end
326
- names
327
- end
328
282
 
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
283
+ def child_symbols_for(decl)
284
+ case decl
285
+ when AST::StructDecl
286
+ field_children = (decl.fields&.map { |f| child_field_symbol(f) } || []).compact
287
+ nested_children = (decl.nested_types&.map { |n| child_nested_struct_symbol(n) } || []).compact
288
+ event_children = (decl.events&.map { |e| child_event_symbol(e) } || []).compact
289
+ field_children + nested_children + event_children
290
+ when AST::UnionDecl
291
+ (decl.fields&.map { |f| child_field_symbol(f) } || []).compact
292
+ when AST::EnumDecl, AST::FlagsDecl
293
+ (decl.members&.map { |m| child_member_symbol(m, default_line: decl.line) } || []).compact
294
+ when AST::VariantDecl
295
+ (decl.arms&.map { |a| child_variant_arm_symbol(a, default_line: decl.line) } || []).compact
296
+ when AST::InterfaceDecl
297
+ (decl.methods&.map { |m| child_method_symbol(m) } || []).compact
298
+ when AST::ExtendingBlock
299
+ (decl.methods&.map { |m| child_method_symbol(m) } || []).compact
300
+ else nil
301
+ end
302
+ end
342
303
 
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
304
+ def child_nested_struct_symbol(nested)
305
+ return nil unless nested.respond_to?(:name) && nested.name && nested.line
359
306
 
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
307
+ grandchildren = child_symbols_for(nested)
308
+ {
309
+ name: nested.name, kind: 23,
310
+ range: { start: { line: nested.line - 1, character: 0 }, end: { line: nested.line, character: 0 } },
311
+ selectionRange: {
312
+ start: { line: nested.line - 1, character: (nested.column ? nested.column - 1 : 0) },
313
+ end: { line: nested.line - 1, character: (nested.column ? nested.column - 1 + nested.name.length : 0) },
314
+ },
315
+ }.tap { |s| s[:children] = grandchildren if grandchildren&.any? }
316
+ end
374
317
 
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
318
+ def collect_nested_type_names(decl)
319
+ names = []
320
+ decl.nested_types.each do |nested|
321
+ names << nested.name
322
+ names.concat(collect_nested_type_names(nested))
323
+ end
324
+ names
325
+ end
388
326
 
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
- []
327
+ def child_field_symbol(f)
328
+ return nil unless f.respond_to?(:name) && f.name && f.line
329
+ detail = f.respond_to?(:type) ? type_detail_string(f.type) : nil
330
+ {
331
+ name: f.name, kind: 8,
332
+ detail: detail,
333
+ range: { start: { line: f.line - 1, character: 0 }, end: { line: f.line, character: 0 } },
334
+ selectionRange: {
335
+ start: { line: f.line - 1, character: (f.column ? f.column - 1 : 0) },
336
+ end: { line: f.line - 1, character: (f.column ? f.column - 1 + f.name.length : 0) },
337
+ },
338
+ }.compact
419
339
  end
420
- end
421
340
 
422
- def local_decl_symbol(decl)
423
- return nil unless decl.name
424
- return nil if decl.name == '_'
341
+ def child_event_symbol(e)
342
+ return nil unless e.respond_to?(:name) && e.name && e.line
425
343
 
426
- line = decl.line || 0
427
- col = decl.column || 1
428
- detail = decl.respond_to?(:type) ? type_detail_string(decl.type) : nil
344
+ parts = ["event[#{e.capacity}]"]
345
+ parts << "(#{type_detail_string(e.payload_type)})" if e.respond_to?(:payload_type) && e.payload_type
429
346
 
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) }
347
+ {
348
+ name: e.name, kind: 24,
349
+ detail: parts.join,
350
+ range: { start: { line: e.line - 1, character: 0 }, end: { line: e.line, character: 0 } },
351
+ selectionRange: {
352
+ start: { line: e.line - 1, character: (e.column ? e.column - 1 : 0) },
353
+ end: { line: e.line - 1, character: (e.column ? e.column - 1 + e.name.length : 0) },
354
+ },
355
+ }
435
356
  end
436
357
 
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]
358
+ def child_member_symbol(m, default_line: nil)
359
+ line = (m.line) ? m.line : default_line
360
+ return nil unless m.respond_to?(:name) && m.name && line
448
361
 
449
- symbol[:children].flat_map { |c| [c[:name]] + descendant_names(c) }
450
- end
362
+ col = m.column ? m.column : 1
363
+ {
364
+ name: m.name, kind: 22,
365
+ range: { start: { line: line - 1, character: 0 }, end: { line: line, character: 0 } },
366
+ selectionRange: {
367
+ start: { line: line - 1, character: col - 1 },
368
+ end: { line: line - 1, character: col - 1 + m.name.length },
369
+ },
370
+ }
371
+ end
451
372
 
452
- def expand_generic_type_params(decl)
453
- return [] unless decl.respond_to?(:type_params) && decl.type_params
373
+ def child_variant_arm_symbol(a, default_line: nil)
374
+ line = (a.line) ? a.line : default_line
375
+ return nil unless a.respond_to?(:name) && a.name && line
376
+ col = a.column ? a.column : 1
377
+ {
378
+ name: a.name, kind: 22,
379
+ range: { start: { line: line - 1, character: 0 }, end: { line: line, character: 0 } },
380
+ selectionRange: {
381
+ start: { line: line - 1, character: col - 1 },
382
+ end: { line: line - 1, character: col - 1 + a.name.length },
383
+ },
384
+ }
385
+ end
454
386
 
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)
387
+ def collect_local_decls(body)
388
+ return [] unless body
389
+
390
+ case body
391
+ when Array
392
+ body.flat_map { |stmt| collect_local_decls(stmt) }.compact
393
+ when AST::LocalDecl
394
+ [body]
395
+ when AST::IfStmt
396
+ (body.branches || []).flat_map { |b| collect_local_decls(b.body) } +
397
+ collect_local_decls(body.else_body)
398
+ when AST::WhileStmt
399
+ collect_local_decls(body.body)
400
+ when AST::ForStmt
401
+ collect_local_decls(body.body) + body.bindings
402
+ when AST::MatchStmt
403
+ (body.arms || []).flat_map { |arm| collect_local_decls(arm.body) }
404
+ when AST::ParallelBlockStmt
405
+ (body.bodies || []).flat_map { |b| collect_local_decls(b) }
406
+ when AST::DeferStmt
407
+ collect_local_decls(body.body)
408
+ when AST::UnsafeStmt
409
+ collect_local_decls(body.body)
410
+ when AST::WhenStmt
411
+ (body.branches || []).flat_map { |b| collect_local_decls(b.body) } +
412
+ collect_local_decls(body.else_body)
413
+ when AST::ErrorBlockStmt
414
+ collect_local_decls(body.body)
458
415
  else
459
- [tp]
416
+ []
460
417
  end
461
418
  end
462
- end
463
419
 
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
420
+ def local_decl_symbol(decl)
421
+ return nil unless decl.name
422
+ return nil if decl.name == '_'
478
423
 
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
424
+ line = decl.line || 0
425
+ col = decl.column || 1
426
+ detail = decl.respond_to?(:type) ? type_detail_string(decl.type) : nil
484
427
 
485
- def child_method_symbol(m)
486
- return nil unless m.respond_to?(:name) && m.name && m.respond_to?(:line) && m.line
428
+ children = []
429
+ if decl.respond_to?(:value) && decl.value.is_a?(AST::ProcExpr)
430
+ detail ||= proc_signature_detail(decl.value)
431
+ proc_locals = collect_local_decls(decl.value.body)
432
+ children = proc_locals.filter_map { |l| local_decl_symbol(l) }
433
+ end
487
434
 
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}"
435
+ {
436
+ name: decl.name, kind: 13,
437
+ detail: detail,
438
+ range: { start: { line: line - 1, character: col - 1 }, end: { line: line - 1, character: col - 1 + decl.name.length } },
439
+ selectionRange: { start: { line: line - 1, character: col - 1 }, end: { line: line - 1, character: col - 1 + decl.name.length } },
440
+ children: children.any? ? children : nil,
441
+ }.compact
494
442
  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
443
 
506
- def type_detail_string(type)
507
- return nil unless type
444
+ def descendant_names(symbol)
445
+ return [] unless symbol[:children]
508
446
 
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}]"
447
+ symbol[:children].flat_map { |c| [c[:name]] + descendant_names(c) }
521
448
  end
522
- end
523
449
 
524
- def handle_formatting(params)
525
- uri = params['textDocument']['uri']
526
- content = @workspace.get_content(uri)
450
+ def expand_generic_type_params(decl)
451
+ return [] unless decl.respond_to?(:type_params) && decl.type_params
452
+
453
+ decl.type_params.flat_map do |tp|
454
+ if tp.respond_to?(:type_params) && tp.type_params&.any?
455
+ expand_generic_type_params(tp)
456
+ else
457
+ [tp]
458
+ end
459
+ end
460
+ end
527
461
 
528
- formatted = Formatter.format_source(content, path: uri, mode: @format_mode)
529
- line_count = content.count("\n")
462
+ def type_param_child_symbol(tp)
463
+ return nil unless tp.respond_to?(:name) && tp.name
464
+ return nil unless tp.line
530
465
 
531
- [
466
+ col = tp.column ? tp.column : 1
532
467
  {
533
- range: {
534
- start: { line: 0, character: 0 },
535
- end: { line: line_count + 1, character: 0 }
468
+ name: tp.name, kind: 26,
469
+ range: { start: { line: tp.line - 1, character: col - 1 }, end: { line: tp.line - 1, character: col - 1 + tp.name.length } },
470
+ selectionRange: {
471
+ start: { line: tp.line - 1, character: col - 1 },
472
+ end: { line: tp.line - 1, character: col - 1 + tp.name.length },
536
473
  },
537
- newText: formatted
538
474
  }
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 }
475
+ end
551
476
 
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
477
+ def proc_signature_detail(proc_expr)
478
+ params = proc_expr.params&.map { |p| "#{p.name}: #{type_detail_string(p.type)}" }&.join(', ') || ''
479
+ ret = type_detail_string(proc_expr.return_type) || 'void'
480
+ "proc(#{params}) -> #{ret}"
481
+ end
555
482
 
556
- segment = content.byteslice(start_off...end_off).to_s
557
- formatted_segment = Formatter.format_source(segment, path: uri, mode: @format_mode)
483
+ def child_method_symbol(m)
484
+ return nil unless m.respond_to?(:name) && m.name && m.line
558
485
 
559
- [
486
+ detail_parts = []
487
+ detail_parts << 'mut' if m.respond_to?(:kind) && m.kind == :editable_function
488
+ detail_parts << 'static' if m.respond_to?(:kind) && m.kind == :static_function
489
+ detail_parts << 'async' if m.respond_to?(:async) && m.async
490
+ if (ret = type_detail_string(m.return_type))
491
+ detail_parts << "-> #{ret}"
492
+ end
560
493
  {
561
- range: {
562
- start: { line: start_pos['line'], character: start_pos['character'] },
563
- end: { line: end_pos['line'], character: end_pos['character'] }
494
+ name: m.name, kind: 6,
495
+ 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 } },
496
+ selectionRange: {
497
+ start: { line: m.line - 1, character: (m.column ? m.column - 1 : 0) },
498
+ end: { line: m.line - 1, character: (m.column ? m.column - 1 + m.name.length : 0) },
564
499
  },
565
- newText: formatted_segment
566
- }
567
- ]
568
- rescue StandardError => e
569
- warn "Error in rangeFormatting handler: #{e.message}"
570
- []
571
- end
500
+ detail: detail_parts.empty? ? nil : detail_parts.join(' '),
501
+ }.compact
502
+ end
503
+
504
+ def type_detail_string(type)
505
+ return nil unless type
506
+
507
+ case type
508
+ when AST::TypeRef
509
+ type.to_s
510
+ when AST::ProcType
511
+ params = (type.params || []).map { |p| type_detail_string(p.type) }.join(', ')
512
+ ret = type_detail_string(type.return_type) || 'void'
513
+ "proc(#{params}) -> #{ret}"
514
+ when AST::TupleType
515
+ base = "(#{(type.element_types || []).map { |t| type_detail_string(t) }.join(', ')})"
516
+ type.nullable ? "#{base}?" : base
517
+ when AST::DynType
518
+ "dyn[#{type.interface}]"
519
+ end
520
+ end
521
+
522
+ def handle_formatting(params)
523
+ uri = params['textDocument']['uri']
524
+ content = @workspace.get_content(uri)
525
+
526
+ formatted = Formatter.format_source(content, path: uri, mode: @format_mode)
527
+ line_count = content.count("\n")
528
+
529
+ [
530
+ {
531
+ range: {
532
+ start: { line: 0, character: 0 },
533
+ end: { line: line_count + 1, character: 0 }
534
+ },
535
+ newText: formatted
536
+ }
537
+ ]
538
+ rescue StandardError => e
539
+ warn "Error in formatting handler: #{e.message}"
540
+ []
541
+ end
542
+
543
+ def handle_range_formatting(params)
544
+ uri = params['textDocument']['uri']
545
+ content = @workspace.get_content(uri)
546
+ range = params['range'] || {}
547
+ start_pos = range['start'] || { 'line' => 0, 'character' => 0 }
548
+ end_pos = range['end'] || { 'line' => 0, 'character' => 0 }
549
+
550
+ start_off = @workspace.position_to_offset(uri, start_pos['line'], start_pos['character'])
551
+ end_off = @workspace.position_to_offset(uri, end_pos['line'], end_pos['character'])
552
+ return [] if end_off < start_off
553
+
554
+ segment = content.byteslice(start_off...end_off).to_s
555
+ formatted_segment = Formatter.format_source(segment, path: uri, mode: @format_mode)
556
+
557
+ [
558
+ {
559
+ range: {
560
+ start: { line: start_pos['line'], character: start_pos['character'] },
561
+ end: { line: end_pos['line'], character: end_pos['character'] }
562
+ },
563
+ newText: formatted_segment
564
+ }
565
+ ]
566
+ rescue StandardError => e
567
+ warn "Error in rangeFormatting handler: #{e.message}"
568
+ []
569
+ end
572
570
  end
573
571
  end
574
572
  end