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,260 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Workspace
6
+ module WorkspaceCaches
7
+ def get_content(uri)
8
+ @document_state_mutex.synchronize do
9
+ @open_documents[uri] || @indexed_documents[uri] || ''
10
+ end
11
+ end
12
+
13
+ def get_tokens(uri)
14
+ @tokens_cache[uri] ||= lex_document(uri)
15
+ end
16
+
17
+ def get_ast(uri)
18
+ @ast_cache[uri] ||= parse_document(uri)
19
+ end
20
+
21
+ def get_facts(uri, allow_last_good_fallback: true)
22
+ snapshot = get_tooling_snapshot(uri, allow_last_good_fallback:)
23
+ snapshot&.facts
24
+ end
25
+
26
+ def get_tooling_snapshot(uri, allow_last_good_fallback: true)
27
+ total_start = perf_logging? ? monotonic_time : nil
28
+ cache_state = 'miss'
29
+ lock_wait_ms = 0.0
30
+ analyze_ms = 0.0
31
+ snapshot = nil
32
+ last_good_snapshot = nil
33
+ generation = nil
34
+ @facts_cache_mutex.synchronize do
35
+ generation = @facts_generation[uri]
36
+ cached = @tooling_snapshot_cache[uri]
37
+ last_good_snapshot = @last_good_tooling_snapshot_cache[uri]
38
+ if cached
39
+ cache_state = 'hit'
40
+ snapshot = cached
41
+ end
42
+ end
43
+ return snapshot if snapshot
44
+
45
+ compute_snapshot = lambda do
46
+ @facts_cache_mutex.synchronize do
47
+ generation = @facts_generation[uri]
48
+ cached = @tooling_snapshot_cache[uri]
49
+ if cached
50
+ cache_state = 'hit'
51
+ snapshot = cached
52
+ end
53
+ end
54
+
55
+ unless snapshot
56
+ analyze_start = total_start ? monotonic_time : nil
57
+ snapshot = analyze_document(uri)
58
+ analyze_ms = elapsed_ms(analyze_start) if analyze_start
59
+ @facts_cache_mutex.synchronize do
60
+ if @facts_generation[uri] == generation
61
+ @tooling_snapshot_cache[uri] = snapshot if snapshot
62
+ if snapshot&.facts
63
+ @facts_cache[uri] = snapshot.facts
64
+ @last_good_facts_cache[uri] = snapshot.facts
65
+ @last_good_tooling_snapshot_cache[uri] = snapshot
66
+ end
67
+ update_dependency_index(uri, snapshot&.facts) if snapshot
68
+ else
69
+ cache_state = 'stale'
70
+ snapshot = @tooling_snapshot_cache[uri] || @last_good_tooling_snapshot_cache[uri]
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ lock_wait_start = total_start ? monotonic_time : nil
77
+ if @facts_state_mutex.try_lock
78
+ begin
79
+ compute_snapshot.call
80
+ ensure
81
+ @facts_state_mutex.unlock
82
+ end
83
+ elsif allow_last_good_fallback && last_good_snapshot
84
+ cache_state = 'last_good'
85
+ snapshot = last_good_snapshot
86
+ else
87
+ @facts_state_mutex.synchronize do
88
+ lock_wait_ms = elapsed_ms(lock_wait_start) if lock_wait_start
89
+ compute_snapshot.call
90
+ end
91
+ end
92
+ snapshot
93
+ ensure
94
+ if total_start
95
+ result_state = snapshot&.facts.nil? ? 'nil' : 'ok'
96
+ log_perf_breakdown(
97
+ 'workspace/get_tooling_snapshot',
98
+ elapsed_ms(total_start),
99
+ "uri=#{uri} cache=#{cache_state} result=#{result_state} stages_ms=lock_wait:#{lock_wait_ms},analyze:#{analyze_ms}",
100
+ )
101
+ end
102
+ end
103
+
104
+ def get_symbols(uri)
105
+ @symbols_cache[uri] ||= extract_symbols_from_tokens(uri)
106
+ end
107
+
108
+ def doc_comment_for_definition(uri, token)
109
+ return nil unless token
110
+
111
+ doc_comment_data_for_definition(uri, token)&.fetch(:raw_markdown, nil)
112
+ end
113
+
114
+ def doc_comment_data_for_definition(uri, token)
115
+ return nil unless token
116
+
117
+ docs_by_location = @doc_comments_cache[uri] ||= extract_doc_comments_for_definitions(uri)
118
+ docs_by_location[doc_comment_key(token.line, token.column)]
119
+ end
120
+
121
+ def position_to_offset(uri, line, char)
122
+ content = get_content(uri)
123
+ line_char_to_offset(content, line, char)
124
+ end
125
+
126
+ def all_documents
127
+ @document_state_mutex.synchronize do
128
+ (@indexed_documents.keys + @open_documents.keys).uniq
129
+ end
130
+ end
131
+
132
+ def module_name_for_uri(uri)
133
+ @document_module_names[uri]
134
+ end
135
+
136
+ # Return all identifier token locations matching +name+ across all known documents.
137
+ # Uses the identifier index for known documents, falling back to scanning.
138
+ def find_all_references(name)
139
+ refs = []
140
+ indexed_entries = @identifier_index_mutex.synchronize { @identifier_index[name]&.dup }
141
+ if indexed_entries
142
+ indexed_entries.each do |entry|
143
+ refs << {
144
+ uri: entry[:uri],
145
+ range: {
146
+ start: { line: entry[:line] - 1, character: entry[:col] - 1 },
147
+ end: { line: entry[:line] - 1, character: entry[:col] - 1 + name.length },
148
+ },
149
+ }
150
+ end
151
+ end
152
+
153
+ unindexed = all_documents.reject { |uri| @indexed_uris.include?(uri) }
154
+ unless unindexed.empty?
155
+ refs.concat(scan_for_references(name, unindexed))
156
+ end
157
+ refs
158
+ end
159
+
160
+ def find_all_references_in(name, uris)
161
+ refs = []
162
+ uriset = uris.to_set
163
+ indexed_entries = @identifier_index_mutex.synchronize { @identifier_index[name]&.dup }
164
+ if indexed_entries
165
+ indexed_entries.each do |entry|
166
+ next unless uriset.include?(entry[:uri])
167
+
168
+ refs << {
169
+ uri: entry[:uri],
170
+ range: {
171
+ start: { line: entry[:line] - 1, character: entry[:col] - 1 },
172
+ end: { line: entry[:line] - 1, character: entry[:col] - 1 + name.length },
173
+ },
174
+ }
175
+ end
176
+ end
177
+
178
+ unindexed = uris.reject { |uri| @indexed_uris.include?(uri) }
179
+ unless unindexed.empty?
180
+ refs.concat(scan_for_references(name, unindexed))
181
+ end
182
+ refs
183
+ end
184
+
185
+ def index_identifier_tokens(uri, tokens)
186
+ return unless tokens
187
+
188
+ @identifier_index_mutex.synchronize do
189
+ @indexed_uris << uri
190
+ tokens.each do |tok|
191
+ next unless tok.type == :identifier
192
+
193
+ (@identifier_index[tok.lexeme] ||= []) << { uri: uri, line: tok.line, col: tok.column }
194
+ end
195
+ end
196
+ end
197
+
198
+ def remove_identifier_index_entries(uri)
199
+ @identifier_index_mutex.synchronize do
200
+ @indexed_uris.delete(uri)
201
+ @identifier_index.each_value do |entries|
202
+ entries.reject! { |entry| entry[:uri] == uri }
203
+ end
204
+ @identifier_index.delete_if { |_name, entries| entries.empty? }
205
+ end
206
+ end
207
+
208
+ private
209
+
210
+ def scan_for_references(name, uris)
211
+ results = []
212
+ uris.each do |doc_uri|
213
+ toks = get_tokens(doc_uri)
214
+ next unless toks
215
+
216
+ toks.each do |tok|
217
+ next unless tok.type == :identifier && tok.lexeme == name
218
+
219
+ results << {
220
+ uri: doc_uri,
221
+ range: {
222
+ start: { line: tok.line - 1, character: tok.column - 1 },
223
+ end: { line: tok.line - 1, character: tok.column - 1 + name.length },
224
+ },
225
+ }
226
+ end
227
+ end
228
+ results
229
+ end
230
+
231
+ # ── Cache management ────────────────────────────────────────────────────
232
+
233
+ def invalidate_cache(uri, clear_last_good: false)
234
+ @tokens_cache.delete(uri)
235
+ @ast_cache.delete(uri)
236
+ @symbols_cache.delete(uri)
237
+ @doc_comments_cache.delete(uri)
238
+ @facts_cache_mutex.synchronize do
239
+ @facts_generation[uri] += 1
240
+ @facts_cache.delete(uri)
241
+ @tooling_snapshot_cache.delete(uri)
242
+ @diagnostics_cache.delete(uri)
243
+ @last_good_facts_cache.delete(uri) if clear_last_good
244
+ @last_good_tooling_snapshot_cache.delete(uri) if clear_last_good
245
+ @last_good_tokens_cache.delete(uri) if clear_last_good
246
+ @document_module_names.delete(uri) if clear_last_good
247
+ delete_dependency_index(uri) if clear_last_good
248
+ end
249
+ remove_identifier_index_entries(uri)
250
+ @definition_cache_mutex.synchronize do
251
+ @definition_index.each_value { |entries| entries.delete_if { |e| e[:uri] == uri } }
252
+ @definition_index.delete_if { |_k, v| v.empty? }
253
+ remove_definition_name_candidates_for_uri(uri)
254
+ @definition_miss_cache.clear
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Workspace
6
+ module WorkspaceCollection
7
+ # Return cached diagnostics for +uri+, re-collecting when content or lint tier changes.
8
+ def collect_diagnostics(uri, lint_tier: :full)
9
+ total_start = perf_logging? ? monotonic_time : nil
10
+ content = get_content(uri)
11
+ hash = content.hash
12
+ normalized_lint_tier = Linter.normalize_lint_tier(lint_tier)
13
+ cache_state = 'miss'
14
+ lock_wait_ms = 0.0
15
+ collect_ms = 0.0
16
+ diagnostics = nil
17
+ generation = nil
18
+ cached_snapshot = nil
19
+ @facts_cache_mutex.synchronize do
20
+ generation = @facts_generation[uri]
21
+ cached_snapshot = @tooling_snapshot_cache[uri]
22
+ entry = @diagnostics_cache[uri]
23
+ if entry && entry[:content_hash] == hash && entry[:lint_tier] == normalized_lint_tier
24
+ cache_state = 'hit'
25
+ diagnostics = entry[:diagnostics]
26
+ end
27
+ end
28
+ return diagnostics if diagnostics
29
+
30
+ lock_wait_start = total_start ? monotonic_time : nil
31
+ @facts_state_mutex.synchronize do
32
+ lock_wait_ms = elapsed_ms(lock_wait_start) if lock_wait_start
33
+ @facts_cache_mutex.synchronize do
34
+ generation = @facts_generation[uri]
35
+ cached_snapshot = @tooling_snapshot_cache[uri]
36
+ entry = @diagnostics_cache[uri]
37
+ if entry && entry[:content_hash] == hash && entry[:lint_tier] == normalized_lint_tier
38
+ cache_state = 'hit'
39
+ diagnostics = entry[:diagnostics]
40
+ end
41
+ end
42
+
43
+ unless diagnostics
44
+ collect_start = total_start ? monotonic_time : nil
45
+ result = Diagnostics.collect(
46
+ uri,
47
+ content,
48
+ shared_module_cache: @shared_module_cache,
49
+ source_overrides: file_backed_source_overrides,
50
+ workspace_root_path: @workspace_root_path,
51
+ dependency_resolution_mode: @dependency_resolution_mode,
52
+ platform_override: @platform_override,
53
+ sema_snapshot: cached_snapshot,
54
+ strict_current_root_diagnostics: @strict_current_root_diagnostics_enabled,
55
+ lint_tier: normalized_lint_tier,
56
+ )
57
+ collect_ms = elapsed_ms(collect_start) if collect_start
58
+ diagnostics = result[:diagnostics]
59
+ facts = result[:facts]
60
+ snapshot = result[:sema_snapshot]
61
+ @facts_cache_mutex.synchronize do
62
+ if @facts_generation[uri] == generation
63
+ @tooling_snapshot_cache[uri] = snapshot if snapshot
64
+ @last_good_tooling_snapshot_cache[uri] = snapshot if snapshot&.facts
65
+ @facts_cache[uri] = facts if facts
66
+ @last_good_facts_cache[uri] = facts if facts
67
+ @document_module_names[uri] = facts.module_name if facts&.module_name
68
+ update_dependency_index(uri, facts)
69
+ @diagnostics_cache[uri] = {
70
+ content_hash: hash,
71
+ lint_tier: normalized_lint_tier,
72
+ diagnostics: diagnostics,
73
+ }
74
+ else
75
+ cache_state = 'stale'
76
+ end
77
+ end
78
+ end
79
+ end
80
+ diagnostics
81
+ rescue StandardError => e
82
+ cache_state = 'error'
83
+ warn " #{e.backtrace.first(6).join("\n ")}" if e.backtrace
84
+ []
85
+ ensure
86
+ if total_start
87
+ result_count = diagnostics ? diagnostics.length : 0
88
+ log_perf_breakdown(
89
+ 'workspace/collect_diagnostics',
90
+ elapsed_ms(total_start),
91
+ "uri=#{uri} mode=#{normalized_lint_tier} cache=#{cache_state} diagnostics=#{result_count} stages_ms=lock_wait:#{lock_wait_ms},collect:#{collect_ms}",
92
+ )
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Workspace
6
+ module WorkspaceDefinitionIndex
7
+ # Find a definition token across the whole workspace, preferring +preferred_uri+.
8
+ # Returns { uri:, token: } or nil.
9
+ # Uses demand-driven name lookup to avoid cold full-workspace indexing.
10
+ def find_definition_token_global(name, preferred_uri: nil, before_line: nil, before_char: nil)
11
+ # Check preferred_uri first without going through the index.
12
+ if preferred_uri
13
+ token = find_definition_token(preferred_uri, name, before_line:, before_char:)
14
+ return { uri: preferred_uri, token: token } if token
15
+ end
16
+
17
+ entries = nil
18
+ miss = false
19
+ @definition_cache_mutex.synchronize do
20
+ entries = @definition_index[name]&.dup
21
+ miss = @definition_miss_cache.include?(name)
22
+ end
23
+ if entries
24
+ entries.each do |entry|
25
+ next if entry[:uri] == preferred_uri
26
+
27
+ return entry
28
+ end
29
+ end
30
+
31
+ return nil if miss
32
+
33
+ candidate_definition_uris(name, exclude_uri: preferred_uri).each do |doc_uri|
34
+ token = find_definition_token(doc_uri, name)
35
+ next unless token
36
+
37
+ entry = { uri: doc_uri, token: token }
38
+ cache_definition_entry(name, entry)
39
+ return entry
40
+ end
41
+
42
+ @definition_cache_mutex.synchronize do
43
+ @definition_miss_cache << name
44
+ end
45
+ nil
46
+ end
47
+
48
+ private
49
+
50
+ def candidate_definition_uris(name, exclude_uri: nil)
51
+ matcher = definition_line_matcher(name)
52
+ open_uris, indexed_uris = @document_state_mutex.synchronize do
53
+ [@open_documents.keys, @indexed_documents.keys]
54
+ end
55
+
56
+ ordered_uris = (open_uris + indexed_uris).uniq
57
+ warmed_candidates = nil
58
+ warmed_uris = nil
59
+ @definition_cache_mutex.synchronize do
60
+ warmed_candidates = @definition_candidate_uris[name].dup
61
+ warmed_uris = @definition_names_by_uri.keys.to_set
62
+ end
63
+
64
+ matches = warmed_candidates.to_a.filter_map do |doc_uri|
65
+ next if doc_uri == exclude_uri
66
+
67
+ doc_uri
68
+ end
69
+
70
+ ordered_uris.each do |doc_uri|
71
+ next if doc_uri == exclude_uri
72
+ next if warmed_uris.include?(doc_uri)
73
+
74
+ content = get_content(doc_uri)
75
+ next if content.empty?
76
+ next unless content.match?(matcher)
77
+
78
+ warm_definition_candidates_for_uri(doc_uri, content)
79
+ matches << doc_uri
80
+ end
81
+
82
+ matches.uniq
83
+ end
84
+
85
+ def definition_line_matcher(name)
86
+ /#{DEFINITION_LINE_PREFIX}#{Regexp.escape(name)}\b/
87
+ end
88
+
89
+ def cache_definition_entry(name, entry)
90
+ @definition_cache_mutex.synchronize do
91
+ entries = (@definition_index[name] ||= [])
92
+ return if entries.any? { |existing| existing[:uri] == entry[:uri] && existing[:token].line == entry[:token].line && existing[:token].column == entry[:token].column }
93
+
94
+ entries << entry
95
+ end
96
+ end
97
+
98
+ def start_definition_warmup
99
+ return if @definition_warmup_thread&.alive?
100
+
101
+ @definition_warmup_thread = Thread.new do
102
+ Thread.current.name = 'mt-lsp-def-warmup' if Thread.current.respond_to?(:name=)
103
+
104
+ loop do
105
+ uri = @definition_warmup_queue.pop
106
+ break if uri == :__stop__
107
+
108
+ content = get_content(uri)
109
+ warm_definition_candidates_for_uri(uri, content)
110
+ end
111
+ rescue StandardError => e
112
+ warn "LSP definition warmup error: #{e.message}"
113
+ end
114
+ end
115
+
116
+ def stop_definition_warmup
117
+ worker = @definition_warmup_thread
118
+ return unless worker
119
+
120
+ @definition_warmup_queue << :__stop__
121
+ worker.join(1.0)
122
+ if worker.alive?
123
+ worker.kill
124
+ worker.join
125
+ end
126
+ @definition_warmup_thread = nil
127
+ rescue StandardError => e
128
+ warn "LSP definition warmup shutdown error: #{e.message}"
129
+ end
130
+
131
+ def enqueue_definition_warmup(uri)
132
+ return if uri.nil?
133
+
134
+ start_definition_warmup unless @definition_warmup_thread&.alive?
135
+
136
+ should_enqueue = false
137
+ @definition_cache_mutex.synchronize do
138
+ unless @definition_warmup_enqueued.include?(uri)
139
+ @definition_warmup_enqueued << uri
140
+ should_enqueue = true
141
+ end
142
+ end
143
+
144
+ @definition_warmup_queue << uri if should_enqueue
145
+ end
146
+
147
+ def warm_definition_candidates_for_uri(uri, content)
148
+ names = extract_definition_names(content)
149
+
150
+ @definition_cache_mutex.synchronize do
151
+ remove_definition_name_candidates_for_uri(uri)
152
+ @definition_names_by_uri[uri] = names
153
+ names.each { |name| @definition_candidate_uris[name] << uri }
154
+ @definition_warmup_enqueued.delete(uri)
155
+ @definition_miss_cache.clear unless names.empty?
156
+ end
157
+ end
158
+
159
+ def remove_definition_name_candidates_for_uri(uri)
160
+ names = @definition_names_by_uri.delete(uri)
161
+ return unless names
162
+
163
+ names.each do |name|
164
+ next unless @definition_candidate_uris.key?(name)
165
+
166
+ @definition_candidate_uris[name].delete(uri)
167
+ @definition_candidate_uris.delete(name) if @definition_candidate_uris[name].empty?
168
+ end
169
+ end
170
+
171
+ def extract_definition_names(content)
172
+ return Set.new if content.empty?
173
+
174
+ names = Set.new
175
+ content.each_line do |line|
176
+ match = line.match(DEFINITION_NAME_REGEX)
177
+ names << match[1] if match
178
+ end
179
+ names
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end