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,598 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Server
6
+ module ServerRename
7
+ private
8
+
9
+ def handle_prepare_rename(params)
10
+ uri = params['textDocument']['uri']
11
+ lsp_line = params['position']['line']
12
+ lsp_char = params['position']['character']
13
+
14
+ token = @workspace.find_token_at(uri, lsp_line, lsp_char)
15
+ return nil unless name_like_token?(token)
16
+
17
+ { range: token_to_range(token), placeholder: token.lexeme }
18
+ rescue StandardError => e
19
+ warn "Error in prepareRename handler: #{e.message}"
20
+ nil
21
+ end
22
+
23
+ def name_like_token?(tok)
24
+ return false if tok.nil?
25
+ tok.type == :identifier || Token::KEYWORDS.value?(tok.type)
26
+ end
27
+
28
+ def allow_hover_last_good_fallback?(uri)
29
+ return true unless @workspace.dependency_resolution_mode == :frozen
30
+
31
+ path = uri_to_path(uri)
32
+ return true unless path && File.file?(path)
33
+
34
+ DependencyResolution.resolve(path, mode: @workspace.dependency_resolution_mode).ok?
35
+ rescue StandardError
36
+ true
37
+ end
38
+
39
+ def handle_rename(params)
40
+ uri = params['textDocument']['uri']
41
+ lsp_line = params['position']['line']
42
+ lsp_char = params['position']['character']
43
+ new_name = params['newName'].to_s
44
+
45
+ token = @workspace.find_token_at(uri, lsp_line, lsp_char)
46
+ return nil unless name_like_token?(token)
47
+
48
+ import_alias_changes = import_alias_rename_changes(uri, token, lsp_line, lsp_char, nil, new_name)
49
+ return { changes: import_alias_changes } if import_alias_changes
50
+
51
+ if (facts = @workspace.get_facts(uri))
52
+ sc = struct_field_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
53
+ return { changes: sc } if sc && !sc.empty?
54
+
55
+ mc = method_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
56
+ return { changes: mc } if mc && !mc.empty?
57
+ end
58
+
59
+ enum_member_changes = enum_member_rename_changes(uri, token, lsp_line, lsp_char, nil, new_name)
60
+ return { changes: enum_member_changes } if enum_member_changes
61
+
62
+ if (facts = @workspace.get_facts(uri))
63
+ scoped_changes = scoped_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
64
+ return { changes: scoped_changes } if scoped_changes
65
+
66
+ enum_member_changes = enum_member_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
67
+ return { changes: enum_member_changes } if enum_member_changes
68
+ end
69
+
70
+ workspace_symbol_changes = workspace_symbol_identity_rename_changes(uri, token, lsp_line, lsp_char, new_name)
71
+ return { changes: workspace_symbol_changes } if workspace_symbol_changes
72
+
73
+ if (facts = @workspace.get_facts(uri))
74
+ module_symbol_changes = module_level_symbol_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
75
+ return { changes: module_symbol_changes } if module_symbol_changes
76
+ end
77
+
78
+ lexical_changes = lexical_rename_changes_in_document(uri, token.lexeme, new_name)
79
+ return { changes: lexical_changes } if lexical_changes
80
+
81
+ nil
82
+ rescue StandardError => e
83
+ warn "Error in rename handler: #{e.message}"
84
+ nil
85
+ end
86
+
87
+ def lexical_rename_changes_in_document(uri, name, new_name)
88
+ tokens = @workspace.get_tokens(uri) || []
89
+ edits = tokens.flat_map do |tok|
90
+ result = []
91
+
92
+ if tok.type == :fstring && tok.literal.is_a?(Array)
93
+ tok.literal.each do |part|
94
+ next unless part.is_a?(Hash) && part[:kind] == :expr
95
+ next unless part[:line] && part[:column]
96
+
97
+ expr_tokens = MilkTea::Lexer.lex(part[:source], path: uri_to_path(uri))
98
+ expr_tokens.each do |etok|
99
+ next unless name_like_token?(etok) && etok.lexeme == name
100
+
101
+ result << {
102
+ range: {
103
+ start: { line: part[:line] - 1, character: part[:column] + etok.column - 2 },
104
+ end: { line: part[:line] - 1, character: part[:column] + etok.column - 2 + etok.lexeme.length },
105
+ },
106
+ newText: new_name,
107
+ }
108
+ end
109
+ rescue MilkTea::LexError
110
+ nil
111
+ end
112
+ end
113
+
114
+ if name_like_token?(tok) && tok.lexeme == name
115
+ result << { range: token_to_range(tok), newText: new_name }
116
+ end
117
+
118
+ result
119
+ end
120
+
121
+ return nil if edits.empty?
122
+
123
+ { uri => edits }
124
+ end
125
+
126
+ def workspace_symbol_identity_rename_changes(uri, token, lsp_line, lsp_char, new_name)
127
+ target_location = resolve_definition_location({
128
+ 'textDocument' => { 'uri' => uri },
129
+ 'position' => { 'line' => lsp_line, 'character' => lsp_char },
130
+ }, stages: nil)
131
+ return nil unless target_location
132
+
133
+ target_identity = definition_identity_key(target_location)
134
+ return nil unless target_identity
135
+
136
+ related_uris = @workspace.related_open_document_uris(uri)
137
+ return nil if related_uris.empty?
138
+
139
+ changes = {}
140
+ related_uris.each do |related_uri|
141
+ tokens = @workspace.get_tokens(related_uri) || []
142
+ next if tokens.empty?
143
+
144
+ edits = tokens.filter_map do |candidate|
145
+ next unless name_like_token?(candidate) && candidate.lexeme == token.lexeme
146
+
147
+ candidate_location = resolve_definition_location({
148
+ 'textDocument' => { 'uri' => related_uri },
149
+ 'position' => { 'line' => candidate.line - 1, 'character' => candidate.column - 1 },
150
+ }, stages: nil)
151
+ next unless candidate_location
152
+ next unless definition_identity_key(candidate_location) == target_identity
153
+
154
+ {
155
+ range: token_to_range(candidate),
156
+ newText: new_name,
157
+ }
158
+ end
159
+
160
+ changes[related_uri] = edits unless edits.empty?
161
+ end
162
+
163
+ return nil if changes.empty?
164
+
165
+ changes
166
+ end
167
+
168
+ def definition_identity_key(location)
169
+ uri = location[:uri] || location['uri']
170
+ range = location[:range] || location['range']
171
+ return nil unless uri && range
172
+
173
+ start = range[:start] || range['start']
174
+ return nil unless start
175
+
176
+ line = start[:line] || start['line']
177
+ character = start[:character] || start['character']
178
+ return nil if line.nil? || character.nil?
179
+
180
+ [uri, line, character]
181
+ end
182
+
183
+ def scoped_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
184
+ binding_id = rename_target_binding_id(uri, token, lsp_line, lsp_char, facts)
185
+ unless binding_id
186
+ return import_alias_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
187
+ end
188
+
189
+ ranges = scoped_binding_occurrence_ranges(uri, token.lexeme, facts, binding_id, include_declaration: true)
190
+ return nil if ranges.empty?
191
+
192
+ edits = ranges.map do |range|
193
+ {
194
+ range: {
195
+ start: { line: range[:line] - 1, character: range[:column] - 1 },
196
+ end: { line: range[:line] - 1, character: range[:column] - 1 + range[:length] },
197
+ },
198
+ newText: new_name,
199
+ }
200
+ end
201
+
202
+ { uri => edits }
203
+ end
204
+
205
+ def module_level_symbol_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
206
+ name = token.lexeme
207
+ module_level = facts.functions.key?(name) || facts.values.key?(name) || facts.types.key?(name) || facts.interfaces&.key?(name)
208
+ return nil unless module_level
209
+
210
+ related_uris = @workspace.related_open_document_uris(uri)
211
+ changes = {}
212
+ [uri, *related_uris].uniq.each do |doc_uri|
213
+ edits = []
214
+ doc_tokens = @workspace.get_tokens(doc_uri) || []
215
+ doc_tokens.each do |tok|
216
+ next unless tok.type == :identifier && tok.lexeme == name
217
+ edits << { range: token_to_range(tok), newText: new_name }
218
+ end
219
+ changes[doc_uri] = edits unless edits.empty?
220
+ end
221
+
222
+ return nil if changes.empty?
223
+ changes
224
+ end
225
+
226
+ def import_alias_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
227
+ alias_name = token.lexeme
228
+ if facts
229
+ return nil unless facts.imports.key?(alias_name)
230
+ end
231
+
232
+ ast = @workspace.get_ast(uri)
233
+ return nil unless ast
234
+
235
+ import_node = ast.imports.find do |imp|
236
+ (imp.alias_name || imp.path.parts.last) == alias_name
237
+ end
238
+ return nil unless import_node
239
+
240
+ # Validate the cursor is at the declaration site or a module-qualifier usage (alias followed by dot).
241
+ tokens = @workspace.get_tokens(uri) || []
242
+
243
+ cursor_at_declaration = token.line == import_node.line && token.column == import_node.column
244
+ unless cursor_at_declaration
245
+ # token was already located by find_token_at; verify it's used as a module qualifier
246
+ tok_idx = tokens.index { |t| t.line == token.line && t.column == token.column }
247
+ return nil unless tok_idx && tokens[tok_idx + 1]&.type == :dot
248
+ end
249
+
250
+ edits = []
251
+
252
+ # Declaration site
253
+ decl_char = import_node.column - 1
254
+ edits << {
255
+ range: {
256
+ start: { line: import_node.line - 1, character: decl_char },
257
+ end: { line: import_node.line - 1, character: decl_char + alias_name.length },
258
+ },
259
+ newText: new_name,
260
+ }
261
+
262
+ # Usage sites: every token with this lexeme immediately followed by a dot
263
+ tokens.each_with_index do |tok, i|
264
+ next unless tok.type == :identifier && tok.lexeme == alias_name
265
+ next unless tokens[i + 1]&.type == :dot
266
+
267
+ edits << {
268
+ range: {
269
+ start: { line: tok.line - 1, character: tok.column - 1 },
270
+ end: { line: tok.line - 1, character: tok.column - 1 + alias_name.length },
271
+ },
272
+ newText: new_name,
273
+ }
274
+ end
275
+
276
+ { uri => edits }
277
+ end
278
+
279
+ def enum_member_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
280
+ tokens = @workspace.get_tokens(uri) || []
281
+
282
+ cursor_index = tokens.index { |t| t.line == token.line && t.column == token.column }
283
+ return nil unless cursor_index
284
+
285
+ cursor_is_enum_member = variant_enum_member_declaration?(tokens, cursor_index) ||
286
+ type_name_member_access?(tokens, cursor_index, facts)
287
+ return nil unless cursor_is_enum_member
288
+
289
+ edits = tokens.each_with_index.filter_map do |tok, i|
290
+ next unless tok.type == :identifier && tok.lexeme == token.lexeme
291
+
292
+ enum_member_decl = variant_enum_member_declaration?(tokens, i)
293
+ enum_member_access = type_name_member_access?(tokens, i, facts)
294
+ next unless enum_member_decl || enum_member_access
295
+
296
+ {
297
+ range: token_to_range(tok),
298
+ newText: new_name,
299
+ }
300
+ end
301
+
302
+ return nil if edits.empty?
303
+
304
+ { uri => edits }
305
+ end
306
+
307
+ def scoped_local_reference_locations(uri, token, lsp_line, lsp_char, facts, include_declaration:)
308
+ binding_id = rename_target_binding_id(uri, token, lsp_line, lsp_char, facts)
309
+ return nil unless binding_id
310
+ return nil unless local_binding_id?(uri, facts, binding_id)
311
+
312
+ ranges = scoped_binding_occurrence_ranges(uri, token.lexeme, facts, binding_id, include_declaration: include_declaration)
313
+ ranges.map do |range|
314
+ {
315
+ uri: uri,
316
+ range: {
317
+ start: { line: range[:line] - 1, character: range[:column] - 1 },
318
+ end: { line: range[:line] - 1, character: range[:column] - 1 + range[:length] },
319
+ },
320
+ }
321
+ end
322
+ end
323
+
324
+ def local_binding_id?(uri, facts, binding_id)
325
+ ast = @workspace.get_ast(uri)
326
+ return false unless ast
327
+
328
+ resolution = facts.binding_resolution
329
+ return false unless resolution
330
+
331
+ each_ast_node(ast) do |node|
332
+ next unless local_binding_declaration_node?(node)
333
+ next unless resolution.declaration_binding_ids[node.object_id] == binding_id
334
+
335
+ return true
336
+ end
337
+
338
+ false
339
+ end
340
+
341
+ def local_binding_declaration_node?(node)
342
+ node.is_a?(AST::LocalDecl) ||
343
+ node.is_a?(AST::Param) ||
344
+ node.is_a?(AST::ForBinding) ||
345
+ node.is_a?(AST::MatchArm) ||
346
+ node.is_a?(AST::MatchExprArm)
347
+ end
348
+
349
+ def rename_target_binding_id(uri, token, lsp_line, lsp_char, facts)
350
+ resolution = facts.binding_resolution
351
+ return nil unless resolution
352
+
353
+ line = lsp_line + 1
354
+ char = lsp_char + 1
355
+ ast = @workspace.get_ast(uri)
356
+ return nil unless ast
357
+
358
+ identifier_node = find_identifier_ast_node_at(ast, token.lexeme, line, char)
359
+ if identifier_node && (id = resolution.identifier_binding_ids[identifier_node.object_id])
360
+ return id
361
+ end
362
+
363
+ declaration_node = find_declaration_ast_node_at(ast, token.lexeme, line, char)
364
+ if declaration_node && (id = resolution.declaration_binding_ids[declaration_node.object_id])
365
+ return id
366
+ end
367
+
368
+ resolve_local_hover_binding(facts, token.lexeme, line, char)&.id
369
+ end
370
+
371
+ def scoped_binding_occurrence_ranges(uri, name, facts, binding_id, include_declaration:)
372
+ ast = @workspace.get_ast(uri)
373
+ return [] unless ast
374
+
375
+ resolution = facts.binding_resolution
376
+ return [] unless resolution
377
+
378
+ ranges = []
379
+ seen = Set.new
380
+
381
+ each_ast_node(ast) do |node|
382
+ range = nil
383
+
384
+ if node.is_a?(AST::Identifier) && node.name == name && resolution.identifier_binding_ids[node.object_id] == binding_id
385
+ range = ast_name_range(node.name, node.line, node.column)
386
+ elsif include_declaration && resolution.declaration_binding_ids[node.object_id] == binding_id
387
+ range = declaration_name_range(node)
388
+ end
389
+
390
+ next unless range
391
+
392
+ key = [range[:line], range[:column], range[:length]]
393
+ next if seen.include?(key)
394
+
395
+ seen << key
396
+ ranges << range
397
+ end
398
+
399
+ ranges
400
+ end
401
+
402
+ def find_identifier_ast_node_at(ast, name, line, char)
403
+ each_ast_node(ast) do |node|
404
+ next unless node.is_a?(AST::Identifier)
405
+ next unless node.name == name
406
+ next unless node.line == line
407
+ next unless char >= node.column && char < node.column + name.length
408
+
409
+ return node
410
+ end
411
+
412
+ nil
413
+ end
414
+
415
+ def find_declaration_ast_node_at(ast, name, line, char)
416
+ each_ast_node(ast) do |node|
417
+ range = declaration_name_range(node)
418
+ next unless range
419
+ next unless range[:name] == name
420
+ next unless range[:line] == line
421
+ next unless char >= range[:column] && char < range[:column] + range[:length]
422
+
423
+ return node
424
+ end
425
+
426
+ nil
427
+ end
428
+
429
+ def declaration_name_range(node)
430
+ if node.is_a?(AST::MatchArm) || node.is_a?(AST::MatchExprArm)
431
+ return nil unless node.binding_name && node.binding_line && node.binding_column
432
+
433
+ return {
434
+ name: node.binding_name,
435
+ line: node.binding_line,
436
+ column: node.binding_column,
437
+ length: node.binding_name.length,
438
+ }
439
+ end
440
+
441
+ return nil if node.is_a?(AST::ForStmt)
442
+ return nil unless node.respond_to?(:name) && node.respond_to?(:line) && node.respond_to?(:column)
443
+ return nil unless node.name.is_a?(String) && node.line && node.column
444
+
445
+ ast_name_range(node.name, node.line, node.column)
446
+ end
447
+
448
+ def ast_name_range(name, line, column)
449
+ { name: name, line: line, column: column, length: name.length }
450
+ end
451
+
452
+ def each_ast_node(node, &block)
453
+ return if node.nil?
454
+
455
+ if node.is_a?(Array)
456
+ node.each { |item| each_ast_node(item, &block) }
457
+ return
458
+ end
459
+
460
+ class_name = node.class.name
461
+ return unless class_name && class_name.start_with?('MilkTea::AST::')
462
+
463
+ yield node
464
+
465
+ if node.respond_to?(:members)
466
+ node.members.each do |member|
467
+ next unless member.is_a?(Symbol)
468
+ each_ast_node(node.public_send(member), &block)
469
+ end
470
+ end
471
+ end
472
+
473
+ def struct_field_rename_changes(uri, token, _lsp_line, _lsp_char, facts, new_name)
474
+ field_name = token.lexeme
475
+ tokens = @workspace.get_tokens(uri) || []
476
+ idx = tokens.index { |t| t.line == token.line && t.column == token.column }
477
+ return nil unless idx
478
+
479
+ cursor_in_field_context = struct_field_cursor_context?(tokens, idx)
480
+ return nil unless cursor_in_field_context
481
+
482
+ struct_type = find_struct_containing_field(facts, field_name)
483
+ return nil unless struct_type
484
+
485
+ edits = []
486
+ tokens.each_with_index do |tok, i|
487
+ next unless tok.type == :identifier && tok.lexeme == field_name
488
+ next unless struct_field_edit_context?(tokens, i, struct_type)
489
+
490
+ edits << { range: token_to_range(tok), newText: new_name }
491
+ end
492
+
493
+ return nil if edits.empty?
494
+ { uri => edits }
495
+ end
496
+
497
+ def struct_field_cursor_context?(tokens, index)
498
+ next_nt = skip_trivia_forward(tokens, index + 1)
499
+ return true if next_nt && tokens[next_nt]&.type == :colon
500
+
501
+ return true if index > 0 && tokens[index - 1].type == :dot
502
+
503
+ if index > 0 && (tokens[index - 1].type == :lparen || tokens[index - 1].type == :comma)
504
+ next_nt = skip_trivia_forward(tokens, index + 1)
505
+ return true if next_nt && (tokens[next_nt]&.type == :equal || tokens[next_nt]&.type == :colon)
506
+ return true if next_nt.nil? || !(tokens[next_nt]&.type == :equal || tokens[next_nt]&.type == :colon)
507
+ end
508
+
509
+ false
510
+ end
511
+
512
+ def struct_field_edit_context?(tokens, index, _struct_type)
513
+ return true if index > 0 && tokens[index - 1].type == :dot
514
+
515
+ next_nt = skip_trivia_forward(tokens, index + 1)
516
+ return true if next_nt && tokens[next_nt]&.type == :colon
517
+
518
+ if index > 0 && (tokens[index - 1].type == :lparen || tokens[index - 1].type == :comma)
519
+ next_nt = skip_trivia_forward(tokens, index + 1)
520
+ return true if next_nt.nil? || (tokens[next_nt]&.type == :equal || tokens[next_nt]&.type == :colon) || tokens[next_nt]&.type == :comma || tokens[next_nt]&.type == :rparen
521
+ end
522
+
523
+ false
524
+ end
525
+
526
+ def skip_trivia_forward(tokens, start_index)
527
+ i = start_index
528
+ while i < tokens.length
529
+ return i unless tokens[i].type == :whitespace || tokens[i].type == :newline || tokens[i].type == :comment
530
+ i += 1
531
+ end
532
+ nil
533
+ end
534
+
535
+ def find_struct_containing_field(facts, field_name)
536
+ facts.types.each_value do |type|
537
+ target = if type.is_a?(Types::GenericStructDefinition)
538
+ type
539
+ elsif type.is_a?(Types::Struct) || type.is_a?(Types::StructInstance)
540
+ type.is_a?(Types::StructInstance) ? type.definition : type
541
+ else
542
+ next
543
+ end
544
+ next unless target.respond_to?(:fields) && target.fields.key?(field_name)
545
+ return type
546
+ end
547
+ nil
548
+ end
549
+
550
+ def method_rename_changes(uri, token, _lsp_line, _lsp_char, facts, new_name)
551
+ method_name = token.lexeme
552
+ method_info = find_method_at_position(uri, token, facts)
553
+ return nil unless method_info
554
+
555
+ receiver_type = method_info[:receiver_type]
556
+ return nil unless receiver_type
557
+
558
+ edits = []
559
+ doc_tokens = @workspace.get_tokens(uri) || []
560
+ doc_tokens.each_with_index do |tok, i|
561
+ next unless tok.type == :identifier && tok.lexeme == method_name
562
+ next unless i > 0 && doc_tokens[i - 1].type == :dot
563
+
564
+ edits << { range: token_to_range(tok), newText: new_name }
565
+ end
566
+
567
+ return nil if edits.empty?
568
+ { uri => edits }
569
+ end
570
+
571
+ def find_method_at_position(uri, token, facts)
572
+ ast = @workspace.get_ast(uri)
573
+ return nil unless ast
574
+
575
+ each_ast_node(ast) do |node|
576
+ next unless node.is_a?(AST::MethodDef)
577
+ next unless node.line == token.line
578
+ next unless token.column >= node.column && token.column < node.column + node.name.length
579
+
580
+ type_name = find_extending_type_for_method(ast, node)
581
+ next unless type_name
582
+
583
+ { receiver_type: facts.types[type_name], method_name: node.name }
584
+ end
585
+ nil
586
+ end
587
+
588
+ def find_extending_type_for_method(ast, method_node)
589
+ each_ast_node(ast) do |node|
590
+ next unless node.is_a?(AST::ExtendingBlock)
591
+ return node.type_name if node.methods.any? { |m| m.object_id == method_node.object_id }
592
+ end
593
+ nil
594
+ end
595
+ end
596
+ end
597
+ end
598
+ end