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,355 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class SemanticAnalyzer
5
+ class Checker
6
+ private
7
+
8
+ def current_actual_scope(scopes)
9
+ scopes.reverse_each do |scope|
10
+ return scope unless scope.is_a?(FlowScope)
11
+ end
12
+
13
+ raise_sema_error("missing lexical scope")
14
+ end
15
+
16
+ def apply_continuation_refinements!(scopes, refinements)
17
+ return if refinements.nil? || refinements.empty?
18
+
19
+ scopes.replace(scopes_with_refinements(scopes, refinements))
20
+ end
21
+
22
+ def scopes_with_refinements(scopes, refinements)
23
+ return scopes if refinements.nil? || refinements.empty?
24
+
25
+ base_scopes = scopes.last.is_a?(FlowScope) ? scopes[0...-1] : scopes
26
+ merged_refinements = if scopes.last.is_a?(FlowScope)
27
+ scopes.last.each_with_object({}) do |(name, binding), result|
28
+ result[name] = if name.is_a?(String) && binding.respond_to?(:type)
29
+ binding.type
30
+ else
31
+ binding
32
+ end
33
+ end
34
+ else
35
+ {}
36
+ end
37
+ merged_refinements = merge_refinements(merged_refinements, refinements)
38
+ flow_scope = FlowScope.new
39
+
40
+ merged_refinements.each do |name, refined_type|
41
+ unless name.is_a?(String)
42
+ flow_scope[name] = refined_type
43
+ next
44
+ end
45
+
46
+ binding = lookup_value(name, base_scopes)
47
+ next unless binding
48
+
49
+ flow_scope[name] = binding.with_flow_type(refined_type)
50
+ end
51
+
52
+ return base_scopes if flow_scope.empty?
53
+
54
+ base_scopes + [flow_scope]
55
+ end
56
+
57
+ def merge_refinements(existing, incoming)
58
+ merged = existing.dup
59
+ incoming.each do |name, refined_type|
60
+ if merged.key?(name) && merged[name] != refined_type
61
+ merged.delete(name)
62
+ else
63
+ merged[name] = refined_type
64
+ end
65
+ end
66
+
67
+ merged
68
+ end
69
+
70
+ def flow_refinements(expression, truthy:, scopes:)
71
+ case expression
72
+ when AST::Call
73
+ if truthy && has_attribute_refinement_call?(expression)
74
+ key = attribute_presence_key_from_call(expression, scopes:)
75
+ return key ? { key => true } : {}
76
+ end
77
+ when AST::UnaryOp
78
+ return flow_refinements(expression.operand, truthy: !truthy, scopes:) if expression.operator == "not"
79
+ when AST::BinaryOp
80
+ case expression.operator
81
+ when "and"
82
+ if truthy
83
+ left_truthy = flow_refinements(expression.left, truthy: true, scopes:)
84
+ right_scopes = scopes_with_refinements(scopes, left_truthy)
85
+ right_truthy = flow_refinements(expression.right, truthy: true, scopes: right_scopes)
86
+ return merge_refinements(left_truthy, right_truthy)
87
+ end
88
+ when "or"
89
+ unless truthy
90
+ left_falsy = flow_refinements(expression.left, truthy: false, scopes:)
91
+ right_scopes = scopes_with_refinements(scopes, left_falsy)
92
+ right_falsy = flow_refinements(expression.right, truthy: false, scopes: right_scopes)
93
+ return merge_refinements(left_falsy, right_falsy)
94
+ end
95
+ when "==", "!="
96
+ return null_test_refinements(expression, truthy:, scopes:)
97
+ end
98
+ end
99
+
100
+ {}
101
+ end
102
+
103
+ def start_local_completion_frame(binding, scopes)
104
+ frame = {
105
+ function_name: binding.name,
106
+ receiver_type: binding.type.receiver_type,
107
+ snapshots: [],
108
+ }
109
+ @active_local_completion_stack << frame
110
+ record_local_completion_snapshot(binding.ast.respond_to?(:line) ? binding.ast.line : nil, 0, scopes)
111
+ end
112
+
113
+ def finish_local_completion_frame(binding)
114
+ return if @active_local_completion_stack.empty?
115
+
116
+ frame = @active_local_completion_stack.pop
117
+ snapshots = frame[:snapshots]
118
+ if snapshots.empty?
119
+ return
120
+ end
121
+
122
+ start_line = [binding.ast.respond_to?(:line) ? binding.ast.line : nil, snapshots.first.line].compact.min
123
+ end_line = snapshots.last.line
124
+
125
+ @local_completion_frames << LocalCompletionFrame.new(
126
+ start_line:,
127
+ end_line:,
128
+ function_name: frame[:function_name],
129
+ receiver_type: frame[:receiver_type],
130
+ snapshots: snapshots.freeze,
131
+ )
132
+ end
133
+
134
+ def record_local_completion_snapshot(line, column, scopes)
135
+ return if @active_local_completion_stack.empty?
136
+ return if line.nil?
137
+
138
+ snapshot = LocalCompletionSnapshot.new(
139
+ line:,
140
+ column: (column || 0),
141
+ bindings: merged_scope_bindings(scopes).freeze,
142
+ )
143
+
144
+ snapshots = @active_local_completion_stack.last[:snapshots]
145
+ prev = snapshots.last
146
+ if prev && prev.line == snapshot.line && prev.column == snapshot.column
147
+ snapshots[-1] = snapshot
148
+ else
149
+ snapshots << snapshot
150
+ end
151
+ end
152
+
153
+ def merged_scope_bindings(scopes)
154
+ scopes.each_with_object({}) do |scope, bindings|
155
+ scope.each do |name, binding|
156
+ next unless name.is_a?(String)
157
+
158
+ bindings[name] = binding
159
+ end
160
+ end
161
+ end
162
+
163
+ def has_attribute_refinement_call?(expression)
164
+ expression.callee.is_a?(AST::Identifier) && expression.callee.name == "has_attribute" && expression.arguments.length == 2 && expression.arguments.none?(&:name)
165
+ end
166
+
167
+ def attribute_presence_key_from_call(expression, scopes:)
168
+ target = resolve_reflection_target_argument(expression.arguments.first.value, scopes:)
169
+ binding = resolve_attribute_name_argument(expression.arguments[1].value)
170
+ validate_attribute_target_compatibility!(target, binding)
171
+ attribute_presence_refinement_key(target, binding)
172
+ rescue SemanticError
173
+ nil
174
+ end
175
+
176
+ def statement_end_line(statement)
177
+ return nil unless statement
178
+
179
+ lines = [statement.respond_to?(:line) ? statement.line : nil]
180
+
181
+ case statement
182
+ when AST::ErrorBlockStmt
183
+ lines.concat(statement_list_lines(statement.body))
184
+ when AST::LocalDecl
185
+ lines << expression_end_line(statement.value) if statement.value
186
+ lines.concat(statement_list_lines(statement.else_body)) if statement.else_body
187
+ when AST::IfStmt
188
+ statement.branches.each do |branch|
189
+ lines << expression_end_line(branch.condition)
190
+ lines.concat(statement_list_lines(branch.body))
191
+ end
192
+ lines.concat(statement_list_lines(statement.else_body)) if statement.else_body
193
+ when AST::UnsafeStmt, AST::ForStmt, AST::WhileStmt
194
+ lines.concat(statement_list_lines(statement.body))
195
+ when AST::MatchStmt
196
+ statement.arms.each do |arm|
197
+ lines.concat(statement_list_lines(arm.body))
198
+ end
199
+ when AST::DeferStmt
200
+ lines.concat(statement_list_lines(statement.body)) if statement.body
201
+ when AST::Assignment
202
+ lines << expression_end_line(statement.value)
203
+ when AST::ReturnStmt
204
+ lines << expression_end_line(statement.value)
205
+ when AST::ExpressionStmt
206
+ lines << expression_end_line(statement.expression)
207
+ when AST::StaticAssert
208
+ lines << expression_end_line(statement.condition)
209
+ end
210
+
211
+ lines.compact.max
212
+ end
213
+
214
+ def statement_list_lines(statements)
215
+ return [] unless statements
216
+
217
+ statements.each_with_object([]) do |stmt, lines|
218
+ end_line = statement_end_line(stmt)
219
+ lines << end_line if end_line
220
+ end
221
+ end
222
+
223
+ def expression_end_line(node)
224
+ return nil unless node
225
+
226
+ lines = [node.respond_to?(:line) ? node.line : nil]
227
+
228
+ case node
229
+ when AST::MemberAccess
230
+ lines << expression_end_line(node.receiver)
231
+ when AST::IndexAccess
232
+ lines << expression_end_line(node.receiver)
233
+ lines << expression_end_line(node.index)
234
+ when AST::Specialization
235
+ lines << expression_end_line(node.callee)
236
+ when AST::Call
237
+ lines << expression_end_line(node.callee)
238
+ node.arguments.each { |argument| lines << expression_end_line(argument.value) }
239
+ when AST::Argument
240
+ lines << expression_end_line(node.value)
241
+ when AST::UnaryOp
242
+ lines << expression_end_line(node.operand)
243
+ when AST::BinaryOp
244
+ lines << expression_end_line(node.left)
245
+ lines << expression_end_line(node.right)
246
+ when AST::IfExpr
247
+ lines << expression_end_line(node.condition)
248
+ lines << expression_end_line(node.then_expression)
249
+ lines << expression_end_line(node.else_expression)
250
+ when AST::MatchExpr
251
+ lines << expression_end_line(node.expression)
252
+ node.arms.each do |arm|
253
+ lines << expression_end_line(arm.pattern)
254
+ lines << expression_end_line(arm.value)
255
+ end
256
+ when AST::AwaitExpr
257
+ lines << expression_end_line(node.expression)
258
+ when AST::FormatExprPart
259
+ lines << expression_end_line(node.expression)
260
+ when AST::PrefixCast
261
+ lines << expression_end_line(node.expression)
262
+ end
263
+
264
+ lines.compact.max
265
+ end
266
+
267
+ def recovered_for_statement(statement)
268
+ AST::ForStmt.new(
269
+ bindings: Array(statement.header_bindings),
270
+ iterables: Array(statement.header_iterables),
271
+ body: statement.body,
272
+ line: statement.line,
273
+ column: statement.column,
274
+ )
275
+ end
276
+
277
+ def null_test_refinements(expression, truthy:, scopes:)
278
+ identifier_expression = nil
279
+ if expression.left.is_a?(AST::Identifier) && expression.right.is_a?(AST::NullLiteral)
280
+ identifier_expression = expression.left
281
+ elsif expression.left.is_a?(AST::NullLiteral) && expression.right.is_a?(AST::Identifier)
282
+ identifier_expression = expression.right
283
+ else
284
+ return {}
285
+ end
286
+
287
+ binding = lookup_value(identifier_expression.name, scopes)
288
+ return {} unless binding&.storage_type.is_a?(Types::Nullable)
289
+
290
+ null_result = expression.operator == "==" ? truthy : !truthy
291
+ refined_type = null_result ? @null_type : binding.storage_type.base
292
+ { identifier_expression.name => refined_type }
293
+ end
294
+
295
+ def conditional_common_type(then_type, else_type, then_expression:, else_expression:)
296
+ return then_type if then_type == else_type
297
+
298
+ numeric_type = common_numeric_type(then_type, else_type)
299
+ return numeric_type if numeric_type
300
+
301
+ if (nullable_type = conditional_null_common_type(then_type, else_type))
302
+ return nullable_type
303
+ end
304
+
305
+ if (nullable_type = conditional_null_common_type(else_type, then_type))
306
+ return nullable_type
307
+ end
308
+
309
+ return then_type if types_compatible?(else_type, then_type, expression: else_expression)
310
+ return else_type if types_compatible?(then_type, else_type, expression: then_expression)
311
+
312
+ nil
313
+ end
314
+
315
+ def nullable_candidate?(type)
316
+ return false if ref_type?(type)
317
+
318
+ sized_layout_type?(type) || pointer_type?(type) || type.is_a?(Types::Nullable)
319
+ end
320
+
321
+ def conditional_null_common_type(null_type, other_type)
322
+ return unless null_type.is_a?(Types::Null)
323
+
324
+ if other_type.is_a?(Types::Nullable)
325
+ return other_type if null_type.target_type.nil? || null_type.target_type == other_type.base
326
+
327
+ return nil
328
+ end
329
+
330
+ return unless nullable_candidate?(other_type)
331
+ return if null_type.target_type && null_type.target_type != other_type
332
+
333
+ Types::Registry.nullable(other_type)
334
+ end
335
+
336
+ def describe_expression(expression)
337
+ case expression
338
+ when AST::Identifier
339
+ expression.name
340
+ when AST::MemberAccess
341
+ "#{describe_expression(expression.receiver)}.#{expression.member}"
342
+ when AST::IndexAccess
343
+ "#{describe_expression(expression.receiver)}[...]"
344
+ when AST::Specialization
345
+ "#{describe_expression(expression.callee)}[...]"
346
+ when AST::FormatString
347
+ 'f"..."'
348
+ else
349
+ expression.class.name.split("::").last
350
+ end
351
+ end
352
+
353
+ end
354
+ end
355
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class SemanticAnalyzer::Checker
5
+ private
6
+
7
+ def validate_consuming_foreign_parameter!(type, function_name:, parameter_name:)
8
+ if type.is_a?(Types::Nullable) || !(opaque_type?(type) || pointer_type?(type))
9
+ raise_sema_error("consuming parameter #{parameter_name} of #{function_name} must use a non-null opaque or ptr[...] type")
10
+ end
11
+ end
12
+
13
+ def foreign_boundary_nullable_pointer_like_base?(base)
14
+ return true if base.is_a?(Types::Function) || base.is_a?(Types::Proc)
15
+ return true if opaque_type?(base)
16
+ return true if base == @ctx.types.fetch("cstr")
17
+
18
+ pointer_type?(base) || const_pointer_type?(base) || ref_type?(base)
19
+ end
20
+
21
+ def reject_foreign_nullable_value_type!(type, function_name:, parameter_name:)
22
+ return unless type.is_a?(Types::Nullable)
23
+ return if foreign_boundary_nullable_pointer_like_base?(type.base)
24
+
25
+ location = parameter_name ? "parameter #{parameter_name}" : "return type"
26
+ raise_sema_error("#{location} of foreign/external function #{function_name} cannot use nullable value type #{type}; nullable at an FFI boundary is only supported for pointer-like types (use ptr[...]? or pass an explicit struct)")
27
+ end
28
+
29
+ def foreign_cstr_boundary_parameter?(parameter)
30
+ parameter.boundary_type == @ctx.types.fetch("cstr") && parameter.type == @ctx.types.fetch("str")
31
+ end
32
+
33
+ def foreign_cstr_argument_compatible?(actual_type, parameter, expression:)
34
+ types_compatible?(actual_type, parameter.type, expression:) || actual_type == @ctx.types.fetch("cstr")
35
+ end
36
+
37
+ def foreign_parameter_boundary_type(param, public_type, type_params:, type_param_constraints: current_type_param_constraints)
38
+ return resolve_type_ref(param.boundary_type, type_params:, type_param_constraints:) if param.boundary_type
39
+ return const_pointer_to(public_type) if param.mode == :in
40
+ return pointer_to(foreign_slot_boundary_value_type(public_type)) if [:out, :inout].include?(param.mode)
41
+
42
+ nil
43
+ end
44
+
45
+ def foreign_slot_boundary_value_type(public_type)
46
+ if public_type.is_a?(Types::Nullable) && pointer_type?(public_type.base)
47
+ return public_type.base
48
+ end
49
+
50
+ public_type
51
+ end
52
+
53
+ def validate_in_foreign_parameter!(public_type, boundary_type, function_name:, parameter_name:)
54
+ unless const_pointer_type?(boundary_type)
55
+ raise_sema_error("in parameter #{parameter_name} of #{function_name} must lower to const_ptr[...], got #{boundary_type || public_type}")
56
+ end
57
+
58
+ expected_public_type = pointee_type(boundary_type)
59
+ return if expected_public_type == public_type
60
+ return if expected_public_type == @ctx.types.fetch("void")
61
+ return if foreign_identity_projection_compatible?(public_type, expected_public_type)
62
+
63
+ raise_sema_error("in parameter #{parameter_name} of #{function_name} cannot map #{public_type} as #{boundary_type}")
64
+ end
65
+
66
+ def foreign_mapping_public_alias_name(name)
67
+ "#{name}_public"
68
+ end
69
+
70
+ def validate_foreign_boundary_type!(public_type, boundary_type, function_name:, parameter_name:)
71
+ return if boundary_type == public_type
72
+ return if boundary_type == @ctx.types.fetch("cstr") && public_type == @ctx.types.fetch("str")
73
+ return if foreign_span_boundary_compatible?(public_type, boundary_type)
74
+ return if foreign_char_pointer_buffer_boundary_compatible?(public_type, boundary_type)
75
+ return if foreign_identity_projection_compatible?(public_type, boundary_type)
76
+
77
+ raise_sema_error("foreign parameter #{parameter_name} of #{function_name} cannot map #{public_type} as #{boundary_type}")
78
+ end
79
+
80
+ def foreign_function_binding?(binding)
81
+ binding.ast.is_a?(AST::ForeignFunctionDecl)
82
+ end
83
+
84
+ def foreign_mapping_expression(decl)
85
+ return decl.mapping unless foreign_mapping_auto_call_shorthand?(decl.mapping)
86
+
87
+ AST::Call.new(
88
+ callee: decl.mapping,
89
+ arguments: decl.params.map { |param| AST::Argument.new(name: nil, value: AST::Identifier.new(name: param.name)) },
90
+ )
91
+ end
92
+
93
+ def foreign_mapping_auto_call_shorthand?(expression)
94
+ case expression
95
+ when AST::Identifier
96
+ true
97
+ when AST::MemberAccess
98
+ foreign_mapping_auto_call_shorthand?(expression.receiver)
99
+ when AST::Specialization
100
+ foreign_mapping_auto_call_shorthand?(expression.callee)
101
+ else
102
+ false
103
+ end
104
+ end
105
+
106
+ def foreign_argument_expression(argument)
107
+ if argument.value.is_a?(AST::UnaryOp) && ["out", "in", "inout"].include?(argument.value.operator)
108
+ argument.value.operand
109
+ else
110
+ argument.value
111
+ end
112
+ end
113
+
114
+ def foreign_argument_legacy_passing_mode(argument)
115
+ return nil unless argument.value.is_a?(AST::UnaryOp) && ["out", "in", "inout"].include?(argument.value.operator)
116
+
117
+ argument.value.operator
118
+ end
119
+
120
+ def foreign_argument_actual_type(parameter, argument, scopes:, function_name:, expected_type: parameter.type)
121
+ case parameter.passing_mode
122
+ when :plain
123
+ infer_expression(argument.value, scopes:, expected_type:)
124
+ when :consuming
125
+ foreign_consuming_argument_binding(parameter, argument, scopes:, function_name:)
126
+ parameter.type
127
+ when :in, :out, :inout
128
+ if (legacy_passing_mode = foreign_argument_legacy_passing_mode(argument))
129
+ raise_sema_error("argument #{parameter.name} to #{function_name} must not use #{legacy_passing_mode}; directional passing is declared on #{function_name}")
130
+ end
131
+
132
+ if parameter.passing_mode == :in
133
+ infer_expression(argument.value, scopes:, expected_type: expected_type)
134
+ else
135
+ infer_lvalue(argument.value, scopes:)
136
+ end
137
+ else
138
+ raise_sema_error("unsupported foreign passing mode #{parameter.passing_mode}")
139
+ end
140
+ end
141
+
142
+ def foreign_consuming_argument_binding(parameter, argument, scopes:, function_name:)
143
+ unless argument.value.is_a?(AST::Identifier)
144
+ raise_sema_error("consuming argument #{parameter.name} to #{function_name} must be a bare nullable local or parameter binding")
145
+ end
146
+
147
+ binding = lookup_value(argument.value.name, scopes)
148
+ unless binding && %i[let var param].include?(binding.kind) && binding.storage_type.is_a?(Types::Nullable) && binding.storage_type.base == parameter.type
149
+ raise_sema_error("consuming argument #{parameter.name} to #{function_name} must be a bare nullable local or parameter binding")
150
+ end
151
+
152
+ binding
153
+ end
154
+ end
155
+ end