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,282 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Workspace
6
+ module WorkspaceDependencyGraph
7
+ # Apply a workspace/didChangeWatchedFiles change to the indexed snapshot.
8
+ # Open documents are source-of-truth and are left untouched.
9
+ def apply_watched_file_change(uri, change_type)
10
+ return [] if @document_state_mutex.synchronize { @open_documents.key?(uri) }
11
+
12
+ if change_type.to_i == 3 # Deleted
13
+ @document_state_mutex.synchronize do
14
+ @indexed_documents.delete(uri)
15
+ end
16
+ invalidate_cache(uri)
17
+ return refresh_import_dependent_caches(changed_uri: uri)
18
+ end
19
+
20
+ path = uri_to_path(uri)
21
+ return [] unless path && File.file?(path)
22
+
23
+ @document_state_mutex.synchronize do
24
+ @indexed_documents[uri] = File.read(path)
25
+ end
26
+ invalidate_cache(uri)
27
+ enqueue_definition_warmup(uri)
28
+ affected_uris = refresh_import_dependent_caches(changed_uri: uri)
29
+ if affected_uris.empty? && change_type.to_i == 1 # Created
30
+ affected_uris = refresh_import_dependent_caches
31
+ affected_uris.delete(uri)
32
+ end
33
+ affected_uris
34
+ rescue StandardError => e
35
+ warn "LSP watched-file update error #{uri}: #{e.message}"
36
+ []
37
+ end
38
+
39
+ def refresh_open_document_dependency_caches(changed_uri)
40
+ path = uri_to_path(changed_uri)
41
+ return [] unless path
42
+
43
+ refresh_import_dependent_caches(changed_uri: changed_uri)
44
+ end
45
+
46
+ def open_document_uris
47
+ @document_state_mutex.synchronize do
48
+ @open_documents.keys.dup
49
+ end
50
+ end
51
+
52
+ # Returns open documents connected to +seed_uri+ through the import
53
+ # dependency graph (imports and reverse dependents).
54
+ def related_open_document_uris(seed_uri)
55
+ open_uris = @document_state_mutex.synchronize do
56
+ @open_documents.keys.dup
57
+ end
58
+ return [] if open_uris.empty?
59
+
60
+ @facts_cache_mutex.synchronize do
61
+ open_uris.each do |uri|
62
+ facts = @facts_cache[uri]
63
+ update_dependency_index(uri, facts)
64
+ end
65
+
66
+ open_set = open_uris.to_set
67
+ return [seed_uri] unless open_set.include?(seed_uri)
68
+
69
+ module_to_uris = Hash.new { |hash, key| hash[key] = Set.new }
70
+ open_uris.each do |uri|
71
+ module_name = @dependency_module_name_by_uri[uri]
72
+ module_to_uris[module_name] << uri if module_name
73
+ end
74
+
75
+ visited = Set.new
76
+ queue = [seed_uri]
77
+
78
+ until queue.empty?
79
+ current = queue.shift
80
+ next if visited.include?(current)
81
+
82
+ visited << current
83
+
84
+ current_module = @dependency_module_name_by_uri[current]
85
+ if current_module
86
+ @reverse_import_dependents[current_module].each do |dependent_uri|
87
+ queue << dependent_uri if open_set.include?(dependent_uri) && !visited.include?(dependent_uri)
88
+ end
89
+ end
90
+
91
+ imported_modules = @dependency_imports_by_uri[current] || Set.new
92
+ imported_modules.each do |imported_module_name|
93
+ module_to_uris[imported_module_name].each do |imported_uri|
94
+ queue << imported_uri if open_set.include?(imported_uri) && !visited.include?(imported_uri)
95
+ end
96
+ end
97
+ end
98
+
99
+ visited.to_a
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def clear_dependency_index
106
+ @dependency_module_name_by_uri.clear
107
+ @dependency_imports_by_uri.clear
108
+ @reverse_import_dependents.clear
109
+ @full_reverse_index_built = false
110
+ end
111
+
112
+ def update_dependency_index(uri, facts)
113
+ imported_module_names = if facts
114
+ facts.imports.each_value.filter_map(&:name).to_set
115
+ else
116
+ dependency_index_imports_for(uri)
117
+ end
118
+
119
+ if imported_module_names&.empty?
120
+ imported_module_names = dependency_index_imports_for(uri) || Set.new
121
+ end
122
+ return if imported_module_names.nil? && facts.nil?
123
+
124
+ delete_dependency_index(uri)
125
+
126
+ module_name = facts&.module_name || infer_module_name_for_uri(uri)
127
+ @dependency_module_name_by_uri[uri] = module_name if module_name
128
+ @dependency_imports_by_uri[uri] = imported_module_names
129
+ imported_module_names.each do |module_name|
130
+ @reverse_import_dependents[module_name] << uri
131
+ end
132
+ end
133
+
134
+ def dependency_index_imports_for(uri)
135
+ ast = @ast_cache[uri]
136
+ unless ast
137
+ content = get_content(uri)
138
+ return nil if content.empty?
139
+
140
+ path = uri_to_path(uri)
141
+ ast = if path && File.file?(path)
142
+ MilkTea::Parser.parse_collecting_errors(content, path: uri).ast
143
+ else
144
+ MilkTea::Parser.parse(content, path: uri)
145
+ end
146
+ end
147
+ return nil unless ast
148
+
149
+ ast.imports.map { |import| import.path.to_s }.to_set
150
+ rescue MilkTea::LexError, MilkTea::ParseError
151
+ nil
152
+ end
153
+
154
+ def delete_dependency_index(uri)
155
+ imported_module_names = @dependency_imports_by_uri.delete(uri) || Set.new
156
+ imported_module_names.each do |module_name|
157
+ dependents = @reverse_import_dependents[module_name]
158
+ dependents.delete(uri)
159
+ @reverse_import_dependents.delete(module_name) if dependents.empty?
160
+ end
161
+ @dependency_module_name_by_uri.delete(uri)
162
+ end
163
+
164
+ def dependent_open_document_uris_for(changed_uri, open_document_uris)
165
+ return open_document_uris.reject { |open_uri| open_uri == changed_uri } unless changed_uri
166
+
167
+ module_name = @dependency_module_name_by_uri[changed_uri] || infer_module_name_for_uri(changed_uri)
168
+ return open_document_uris.reject { |open_uri| open_uri == changed_uri } unless module_name
169
+
170
+ dependents = @reverse_import_dependents[module_name]
171
+ open_document_uris.select { |open_uri| open_uri != changed_uri && dependents.include?(open_uri) }
172
+ end
173
+
174
+ def infer_module_name_for_uri(uri)
175
+ path = uri_to_path(uri)
176
+ return nil unless path
177
+
178
+ resolution = DependencyResolution.resolve(path, mode: @dependency_resolution_mode)
179
+ return nil if resolution.error_message
180
+
181
+ loader = ModuleLoader.new(
182
+ module_roots: module_roots_for_path(path, locked: resolution.locked),
183
+ package_graph: package_graph_for_path(path, locked: resolution.locked),
184
+ platform: effective_platform_for_path(path),
185
+ )
186
+ loader.send(:inferred_module_name_for_path, path)
187
+ rescue StandardError
188
+ nil
189
+ end
190
+
191
+ def module_roots_for_path(path, locked: false)
192
+ roots = MilkTea::ModuleRoots.roots_for_path(path, locked: locked)
193
+ return roots unless workspace_root_applicable_for?(path)
194
+
195
+ [@workspace_root_path, *roots].uniq
196
+ end
197
+
198
+ def workspace_root_applicable_for?(path)
199
+ return false if @workspace_root_path.nil? || path.nil?
200
+
201
+ expanded_path = File.expand_path(path)
202
+ expanded_path == @workspace_root_path || expanded_path.start_with?(@workspace_root_path + File::SEPARATOR)
203
+ end
204
+
205
+ def normalize_workspace_root_path(path)
206
+ return nil if path.nil? || path.to_s.strip.empty?
207
+
208
+ expanded_path = File.expand_path(path.to_s)
209
+ return nil unless File.directory?(expanded_path)
210
+
211
+ expanded_path
212
+ end
213
+
214
+ def refresh_import_dependent_caches(changed_uri: nil)
215
+ all_open_document_uris = @document_state_mutex.synchronize do
216
+ @open_documents.keys
217
+ end
218
+ open_document_uris = @facts_cache_mutex.synchronize do
219
+ dependent_open_document_uris_for(changed_uri, all_open_document_uris)
220
+ end
221
+
222
+ @facts_state_mutex.synchronize do
223
+ @facts_cache_mutex.synchronize do
224
+ preserved_open_last_good_facts = all_open_document_uris.each_with_object({}) do |open_uri, preserved|
225
+ facts = @last_good_facts_cache[open_uri]
226
+ preserved[open_uri] = facts if facts
227
+ end
228
+ preserved_open_last_good_snapshots = all_open_document_uris.each_with_object({}) do |open_uri, preserved|
229
+ snapshot = @last_good_tooling_snapshot_cache[open_uri]
230
+ preserved[open_uri] = snapshot if snapshot
231
+ end
232
+ @shared_module_cache.clear
233
+ @facts_cache.clear
234
+ @tooling_snapshot_cache.clear
235
+ @diagnostics_cache.clear
236
+ @last_good_facts_cache.clear
237
+ @last_good_tooling_snapshot_cache.clear
238
+ preserved_open_last_good_snapshots.each do |open_uri, snapshot|
239
+ @last_good_tooling_snapshot_cache[open_uri] = snapshot
240
+ end
241
+ preserved_open_last_good_facts.each do |open_uri, facts|
242
+ @last_good_facts_cache[open_uri] = facts
243
+ end
244
+ end
245
+ end
246
+ open_document_uris
247
+ end
248
+
249
+ public
250
+
251
+ def reverse_import_dependents_for(module_name)
252
+ ensure_full_reverse_import_index
253
+ @reverse_import_dependents[module_name]
254
+ end
255
+
256
+ private
257
+
258
+ def ensure_full_reverse_import_index
259
+ return if @full_reverse_index_built
260
+
261
+ all_uris = @document_state_mutex.synchronize do
262
+ (@indexed_documents.keys + @open_documents.keys).uniq
263
+ end
264
+
265
+ all_uris.each do |uri|
266
+ next if @dependency_imports_by_uri.key?(uri)
267
+
268
+ imported = dependency_index_imports_for(uri)
269
+ next unless imported
270
+
271
+ @dependency_imports_by_uri[uri] = imported
272
+ imported.each do |mod_name|
273
+ @reverse_import_dependents[mod_name] << uri
274
+ end
275
+ end
276
+
277
+ @full_reverse_index_built = true
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Workspace
6
+ module WorkspaceStore
7
+ def set_document_source(uri, source)
8
+ normalized = source.to_s
9
+ raise ArgumentError, "invalid document source #{source.inspect}" unless DOCUMENT_SOURCES.include?(normalized)
10
+
11
+ @document_state_mutex.synchronize do
12
+ previous = @document_sources[uri]
13
+ @document_sources[uri] = normalized
14
+ previous
15
+ end
16
+ end
17
+
18
+ def document_source(uri)
19
+ @document_state_mutex.synchronize do
20
+ @document_sources[uri]
21
+ end
22
+ end
23
+
24
+ def background_document?(uri)
25
+ @document_state_mutex.synchronize do
26
+ @document_sources[uri] == 'background-document'
27
+ end
28
+ end
29
+
30
+ # ── Document lifecycle ──────────────────────────────────────────────────
31
+
32
+ def open_document(uri, content)
33
+ @document_state_mutex.synchronize do
34
+ @open_documents[uri] = content
35
+ end
36
+ invalidate_cache(uri)
37
+ enqueue_definition_warmup(uri) unless background_document?(uri)
38
+ warm_document_facts(uri, content)
39
+ end
40
+
41
+ def close_document(uri)
42
+ # Keep indexed snapshot available for workspace-level features.
43
+ path = uri_to_path(uri)
44
+ @document_state_mutex.synchronize do
45
+ @open_documents.delete(uri)
46
+ @document_sources.delete(uri)
47
+ if path && File.file?(path)
48
+ begin
49
+ @indexed_documents[uri] = File.read(path)
50
+ rescue StandardError
51
+ nil
52
+ end
53
+ end
54
+ end
55
+ invalidate_cache(uri, clear_last_good: true)
56
+ enqueue_definition_warmup(uri) unless background_document?(uri)
57
+ end
58
+
59
+ def apply_incremental_changes(uri, changes)
60
+ content = get_content(uri)
61
+ edits = Array(changes)
62
+
63
+ new_content = if edits.length > 1 && edits.all? { |change| change['range'] }
64
+ apply_incremental_changes_against_snapshot(content, edits)
65
+ else
66
+ edits.reduce(content) { |acc, change| apply_incremental_change_to_content(acc, change) }
67
+ end
68
+
69
+ @document_state_mutex.synchronize do
70
+ @open_documents[uri] = new_content
71
+ end
72
+ invalidate_cache(uri)
73
+ enqueue_definition_warmup(uri) unless background_document?(uri)
74
+
75
+ @shared_module_cache.clear
76
+ dependent_uris = @facts_cache_mutex.synchronize do
77
+ all_open = @document_state_mutex.synchronize { @open_documents.keys }
78
+ dependent_open_document_uris_for(uri, all_open)
79
+ end
80
+ dependent_uris.each { |dep_uri| invalidate_cache(dep_uri) }
81
+
82
+ warm_document_facts(uri, new_content)
83
+ end
84
+
85
+ def apply_incremental_change_to_content(content, change)
86
+ if change['range']
87
+ start_pos = change['range']['start']
88
+ end_pos = change['range']['end']
89
+ start_off = line_char_to_offset(content, start_pos['line'], start_pos['character'])
90
+ end_off = line_char_to_offset(content, end_pos['line'], end_pos['character'])
91
+ prefix = content.byteslice(0, start_off).to_s
92
+ suffix = content.byteslice(end_off..).to_s
93
+ prefix + change['text'].to_s + suffix
94
+ else
95
+ # Full-document fallback within an incremental-sync session
96
+ change['text'].to_s
97
+ end
98
+ end
99
+
100
+ def apply_incremental_changes_against_snapshot(content, changes)
101
+ ordered = changes.sort_by do |change|
102
+ start_pos = change['range']['start']
103
+ [start_pos['line'], start_pos['character']]
104
+ end.reverse
105
+
106
+ ordered.reduce(content) { |acc, change| apply_incremental_change_to_content(acc, change) }
107
+ end
108
+
109
+ # Index all .mt files under root_uri so they are available for workspace-wide queries.
110
+ def index_workspace(root_uri, &progress)
111
+ root_path = uri_to_path(root_uri)
112
+ return unless root_path && File.directory?(root_path)
113
+
114
+ paths = Dir.glob(File.join(root_path, '**', '*.mt')).sort
115
+ total = paths.length
116
+ paths.each_with_index do |path, idx|
117
+ file_uri = path_to_uri(path)
118
+ @document_state_mutex.synchronize do
119
+ @indexed_documents[file_uri] ||= begin
120
+ File.read(path)
121
+ rescue StandardError
122
+ nil
123
+ end
124
+ end
125
+ if progress && total > 0
126
+ pct = ((idx + 1) * 100 / total).clamp(0, 100)
127
+ progress.call(pct, "#{idx + 1}/#{total} files")
128
+ end
129
+ end
130
+ rescue StandardError
131
+ end
132
+
133
+ def rename_indexed_file(old_uri, new_uri)
134
+ @document_state_mutex.synchronize do
135
+ content = @indexed_documents.delete(old_uri)
136
+ if content
137
+ @indexed_documents[new_uri] = content
138
+ end
139
+ @document_sources.delete(old_uri)
140
+ if @indexed_documents.key?(new_uri)
141
+ set_document_source(new_uri, 'workspace-file')
142
+ end
143
+ end
144
+ invalidate_cache(old_uri, clear_last_good: true)
145
+ invalidate_cache(new_uri)
146
+ end
147
+
148
+ def shutdown
149
+ stop_definition_warmup
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end