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,1565 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LowererCalls
5
+ private
6
+
7
+
8
+ def lower_call(expression, env:, type:)
9
+ ct_value = compile_time_const_value(expression, env:)
10
+ if ct_value && (literal = lower_compile_time_literal(ct_value, type))
11
+ if expression.callee.is_a?(AST::Identifier)
12
+ binding = @ctx.functions[expression.callee.name]
13
+ return literal unless binding&.ast&.respond_to?(:const) && binding.ast.const
14
+ else
15
+ return literal
16
+ end
17
+ end
18
+
19
+ kind, callee_name, receiver, callee_type, callee_binding = resolve_callee(expression.callee, env, arguments: expression.arguments)
20
+
21
+ case kind
22
+ when :function
23
+ if callee_binding && foreign_function_binding?(callee_binding)
24
+ raise LoweringError, "consuming foreign calls must be top-level expression statements" if foreign_call_consumes_binding?(callee_binding)
25
+
26
+ return lower_foreign_call_inline(expression, callee_binding, env:, type:)
27
+ end
28
+
29
+ arguments = lower_call_arguments(expression.arguments, callee_type, env:)
30
+ IR::Call.new(callee: callee_name, arguments:, type:)
31
+ when :callable_value
32
+ callee_expression = lower_expression(expression.callee, env:, expected_type: callee_type)
33
+ if proc_type?(callee_type)
34
+ arguments = [
35
+ IR::Member.new(receiver: callee_expression, member: "env", type: proc_env_pointer_type),
36
+ *lower_call_arguments(expression.arguments, callee_type, env:),
37
+ ]
38
+ IR::Call.new(
39
+ callee: IR::Member.new(receiver: callee_expression, member: "invoke", type: proc_invoke_function_type(callee_type)),
40
+ arguments:,
41
+ type:,
42
+ )
43
+ else
44
+ arguments = lower_call_arguments(expression.arguments, callee_type, env:)
45
+ IR::Call.new(callee: callee_expression, arguments:, type:)
46
+ end
47
+ when :method
48
+ receiver_arg = lower_method_receiver_argument(receiver, callee_type, callee_binding, env:)
49
+ arguments = [receiver_arg, *lower_call_arguments(expression.arguments, callee_type, env:)]
50
+ IR::Call.new(callee: callee_name, arguments:, type:)
51
+ when :str_buffer_clear
52
+ IR::Call.new(
53
+ callee: "mt_str_buffer_clear",
54
+ arguments: str_buffer_args(receiver, env:),
55
+ type:,
56
+ )
57
+ when :str_buffer_assign, :str_buffer_assign_format
58
+ IR::Call.new(
59
+ callee: "mt_str_buffer_assign",
60
+ arguments: [
61
+ lower_contextual_expression(expression.arguments.fetch(0).value, env:, expected_type: @ctx.types.fetch("str")),
62
+ *str_buffer_args(receiver, env:),
63
+ ],
64
+ type:,
65
+ )
66
+ when :str_buffer_append, :str_buffer_append_format
67
+ IR::Call.new(
68
+ callee: "mt_str_buffer_append",
69
+ arguments: [
70
+ lower_contextual_expression(expression.arguments.fetch(0).value, env:, expected_type: @ctx.types.fetch("str")),
71
+ *str_buffer_args(receiver, env:),
72
+ ],
73
+ type:,
74
+ )
75
+ when :str_buffer_len
76
+ IR::Call.new(
77
+ callee: "mt_str_buffer_len",
78
+ arguments: str_buffer_args(receiver, env:),
79
+ type:,
80
+ )
81
+ when :str_buffer_capacity
82
+ receiver_type = infer_expression_type(receiver, env:)
83
+ IR::IntegerLiteral.new(value: str_buffer_capacity(receiver_type), type: type)
84
+ when :str_buffer_as_str
85
+ args = str_buffer_args(receiver, env:)
86
+ IR::AggregateLiteral.new(
87
+ type:,
88
+ fields: [
89
+ IR::AggregateField.new(name: "data", value: args[0]),
90
+ IR::AggregateField.new(
91
+ name: "len",
92
+ value: IR::Call.new(
93
+ callee: "mt_str_buffer_len",
94
+ arguments: args,
95
+ type: @ctx.types.fetch("ptr_uint"),
96
+ ),
97
+ ),
98
+ ],
99
+ )
100
+ when :str_buffer_as_cstr
101
+ IR::Call.new(
102
+ callee: "mt_str_buffer_as_cstr",
103
+ arguments: str_buffer_args(receiver, env:),
104
+ type:,
105
+ )
106
+ when :array_as_span
107
+ receiver_type = infer_expression_type(receiver, env:)
108
+ lower_array_to_span_expression(lower_expression(receiver, env:), type)
109
+ when :event_subscribe, :event_subscribe_once, :event_unsubscribe, :event_emit, :event_wait
110
+ event_type = infer_expression_type(receiver, env:)
111
+ runtime = ensure_event_runtime(event_type)
112
+ event_pointer = lower_event_storage_pointer(receiver, env:)
113
+
114
+ case kind
115
+ when :event_subscribe
116
+ lower_event_subscribe_call(expression, env:, runtime:, event_pointer:, type:)
117
+ when :event_subscribe_once
118
+ lower_event_subscribe_call(expression, env:, runtime:, event_pointer:, type:, once: true)
119
+ when :event_unsubscribe
120
+ IR::Call.new(
121
+ callee: runtime.fetch(:unsubscribe_linkage_name),
122
+ arguments: [
123
+ event_pointer,
124
+ lower_contextual_expression(expression.arguments.fetch(0).value, env:, expected_type: @ctx.types.fetch("Subscription")),
125
+ ],
126
+ type:,
127
+ )
128
+ when :event_emit
129
+ arguments = [event_pointer]
130
+ if event_type.payload_type
131
+ arguments << lower_contextual_expression(expression.arguments.fetch(0).value, env:, expected_type: event_type.payload_type)
132
+ end
133
+ IR::Call.new(callee: runtime.fetch(:emit_linkage_name), arguments:, type:)
134
+ when :event_wait
135
+ IR::Call.new(callee: runtime.fetch(:wait_linkage_name), arguments: [event_pointer], type:)
136
+ end
137
+ when :atomic_load, :atomic_store, :atomic_add, :atomic_sub, :atomic_exchange, :atomic_compare_exchange
138
+ lower_atomic_method_call(kind, receiver, expression, env:, type:)
139
+ when :associated_method
140
+ arguments = lower_call_arguments(expression.arguments, callee_type, env:)
141
+ IR::Call.new(callee: callee_name, arguments:, type:)
142
+ when :struct_literal
143
+ fields = expression.arguments.map do |argument|
144
+ field_type = type.field(argument.name)
145
+ lowered_value = lower_contextual_expression(
146
+ argument.value,
147
+ env:,
148
+ expected_type: field_type,
149
+ external_numeric: type.respond_to?(:external) && type.external,
150
+ contextual_int_to_float: contextual_int_to_float_target?(field_type),
151
+ )
152
+ lowered_value = wrap_nullable_field_value(field_type, lowered_value, env)
153
+ IR::AggregateField.new(
154
+ name: argument.name,
155
+ value: lowered_value,
156
+ )
157
+ end
158
+ IR::AggregateLiteral.new(type:, fields:)
159
+ when :struct_with
160
+ explicit_names = expression.arguments.each_with_object({}) { |a, h| h[a.name] = a }
161
+ lowered_receiver = lower_expression(receiver, env:)
162
+ field_hash = callee_type.respond_to?(:fields) ? callee_type.fields : {}
163
+ fields = field_hash.map do |field_name, field_type|
164
+ if (explicit_arg = explicit_names[field_name])
165
+ IR::AggregateField.new(
166
+ name: field_name,
167
+ value: lower_contextual_expression(
168
+ explicit_arg.value,
169
+ env:,
170
+ expected_type: field_type,
171
+ contextual_int_to_float: contextual_int_to_float_target?(field_type),
172
+ ),
173
+ )
174
+ else
175
+ IR::AggregateField.new(
176
+ name: field_name,
177
+ value: IR::Member.new(
178
+ receiver: lowered_receiver,
179
+ member: field_name,
180
+ type: field_type,
181
+ ),
182
+ )
183
+ end
184
+ end
185
+ IR::AggregateLiteral.new(type: callee_type, fields:)
186
+ when :variant_arm_ctor
187
+ _, _, _, variant_type, (_, arm_name) = resolve_callee(expression.callee, env, arguments: expression.arguments)
188
+ arm_fields = variant_type.arm(arm_name)
189
+ provided_names = expression.arguments.map(&:name).to_set
190
+ payload_fields = expression.arguments.map do |argument|
191
+ field_type = arm_fields.fetch(argument.name)
192
+ lowered_value = lower_contextual_expression(
193
+ argument.value,
194
+ env:,
195
+ expected_type: field_type,
196
+ contextual_int_to_float: contextual_int_to_float_target?(field_type),
197
+ )
198
+ lowered_value = IR::AddressOf.new(expression: lowered_value, type: lowered_value.type) if field_type == variant_type
199
+ lowered_value = wrap_nullable_field_value(field_type, lowered_value, env) unless field_type == variant_type
200
+ IR::AggregateField.new(
201
+ name: argument.name,
202
+ value: lowered_value,
203
+ )
204
+ end
205
+ arm_fields.each do |field_name, field_type|
206
+ next if provided_names.include?(field_name)
207
+ next unless field_type.void?
208
+
209
+ payload_fields << IR::AggregateField.new(
210
+ name: field_name,
211
+ value: IR::IntegerLiteral.new(type: @ctx.types.fetch("ubyte"), value: 0),
212
+ )
213
+ end
214
+ IR::VariantLiteral.new(type: variant_type, arm_name:, fields: payload_fields)
215
+ when :array
216
+ element_type = array_element_type(type)
217
+ elements = expression.arguments.map do |argument|
218
+ lower_contextual_expression(argument.value, env:, expected_type: element_type)
219
+ end
220
+ IR::ArrayLiteral.new(type:, elements:)
221
+ when :reinterpret
222
+ argument = expression.arguments.fetch(0)
223
+ source_type = infer_expression_type(argument.value, env:)
224
+ IR::ReinterpretExpr.new(
225
+ target_type: type,
226
+ source_type:,
227
+ expression: lower_expression(argument.value, env:, expected_type: source_type),
228
+ type:,
229
+ )
230
+ when :hash
231
+ resolution = resolve_hash_specialization(expression.callee, env:)
232
+ argument = expression.arguments.fetch(0)
233
+ IR::Call.new(
234
+ callee: resolution.callee_name,
235
+ arguments: [lower_hash_operation_argument(argument.value, env:, target_type: resolution.target_type)],
236
+ type:,
237
+ )
238
+ when :equal
239
+ resolution = resolve_equal_specialization(expression.callee, env:)
240
+ left = expression.arguments.fetch(0)
241
+ right = expression.arguments.fetch(1)
242
+ IR::Call.new(
243
+ callee: resolution.callee_name,
244
+ arguments: [
245
+ lower_hash_operation_argument(left.value, env:, target_type: resolution.target_type),
246
+ lower_hash_operation_argument(right.value, env:, target_type: resolution.target_type),
247
+ ],
248
+ type:,
249
+ )
250
+ when :order
251
+ resolution = resolve_order_specialization(expression.callee, env:)
252
+ left = expression.arguments.fetch(0)
253
+ right = expression.arguments.fetch(1)
254
+ IR::Call.new(
255
+ callee: resolution.callee_name,
256
+ arguments: [
257
+ lower_hash_operation_argument(left.value, env:, target_type: resolution.target_type),
258
+ lower_hash_operation_argument(right.value, env:, target_type: resolution.target_type),
259
+ ],
260
+ type:,
261
+ )
262
+ when :zero
263
+ IR::ZeroInit.new(type:)
264
+ when :fatal
265
+ argument = expression.arguments.fetch(0)
266
+ message_type = infer_expression_type(argument.value, env:)
267
+ callee = message_type == @ctx.types.fetch("cstr") ? "mt_fatal" : "mt_fatal_str"
268
+ IR::Call.new(callee:, arguments: [lower_expression(argument.value, env:, expected_type: message_type)], type:)
269
+ when :get
270
+ receiver_arg = expression.arguments.fetch(0)
271
+ index_arg = expression.arguments.fetch(1)
272
+ receiver_type = infer_expression_type(receiver_arg.value, env:)
273
+ receiver = lower_expression(receiver_arg.value, env:)
274
+ index = lower_expression(index_arg.value, env:)
275
+ if array_type?(receiver_type)
276
+ IR::NullableIndex.new(receiver:, index:, receiver_type:, type:)
277
+ elsif receiver_type.is_a?(Types::Span)
278
+ IR::NullableSpanIndex.new(receiver:, index:, receiver_type:, type:)
279
+ else
280
+ raise LoweringError, "get expects an array or span, got #{receiver_type}"
281
+ end
282
+ when :ref_of
283
+ argument = expression.arguments.fetch(0)
284
+ lower_addr_expression(argument.value, env:, target_type: type)
285
+ when :const_ptr_of
286
+ argument = expression.arguments.fetch(0)
287
+ lower_addr_expression(argument.value, env:, target_type: type)
288
+ when :read
289
+ argument = expression.arguments.fetch(0)
290
+ IR::Unary.new(operator: "*", operand: lower_expression(argument.value, env:), type:)
291
+ when :ptr_of
292
+ argument = expression.arguments.fetch(0)
293
+ argument_type = infer_expression_type(argument.value, env:)
294
+ if ref_type?(argument_type)
295
+ IR::Cast.new(target_type: type, expression: lower_expression(argument.value, env:), type:)
296
+ else
297
+ lower_addr_expression(argument.value, env:, target_type: type)
298
+ end
299
+ when :adapt
300
+ lower_adapt_call(expression, env:, type:, interface: callee_binding)
301
+ when :dyn_method
302
+ lower_dyn_method_call(expression, receiver, callee_type, env:, type:)
303
+ else
304
+ raise LoweringError, "unsupported call kind #{kind}"
305
+ end
306
+ end
307
+
308
+ def lower_method_receiver_argument(receiver, callee_type, callee_binding, env:)
309
+ lowered_receiver = lower_expression(receiver, env:)
310
+ declared_receiver_type = callee_type.receiver_type
311
+
312
+ if pointer_lowered_method_receiver?(callee_type, callee_binding)
313
+ return lowered_receiver if ref_type?(lowered_receiver.type)
314
+ return lowered_receiver if pointer_type?(lowered_receiver.type)
315
+
316
+ if lowered_receiver.is_a?(IR::Name) && lowered_receiver.pointer
317
+ return lowered_receiver
318
+ end
319
+
320
+ if lowered_receiver.is_a?(IR::Unary) && lowered_receiver.operator == "*"
321
+ return lowered_receiver.operand
322
+ end
323
+
324
+ return IR::AddressOf.new(expression: lowered_receiver, type: lowered_receiver.type)
325
+ end
326
+
327
+ return lowered_receiver if declared_receiver_type && pointer_type?(declared_receiver_type)
328
+
329
+ if ref_type?(lowered_receiver.type)
330
+ return IR::Unary.new(operator: "*", operand: lowered_receiver, type: referenced_type(lowered_receiver.type))
331
+ end
332
+
333
+ if lowered_receiver.is_a?(IR::Name) && lowered_receiver.pointer
334
+ return IR::Unary.new(operator: "*", operand: lowered_receiver, type: lowered_receiver.type)
335
+ end
336
+
337
+ if pointer_type?(lowered_receiver.type)
338
+ return IR::Unary.new(operator: "*", operand: lowered_receiver, type: pointee_type(lowered_receiver.type))
339
+ end
340
+
341
+ lowered_receiver
342
+ end
343
+
344
+ def lower_addr_expression(expression, env:, target_type:)
345
+ lowered_expression = lower_expression(expression, env:)
346
+ return cast_expression(lowered_expression, target_type) if lowered_expression.is_a?(IR::Name) && lowered_expression.pointer
347
+
348
+ if lowered_expression.is_a?(IR::Unary) && lowered_expression.operator == "*"
349
+ return cast_expression(lowered_expression.operand, target_type)
350
+ end
351
+
352
+ IR::AddressOf.new(expression: lowered_expression, type: target_type)
353
+ end
354
+
355
+ def lower_hash_operation_argument(expression, env:, target_type:)
356
+ actual_type = infer_expression_type(expression, env:)
357
+ lowered_expression = lower_expression(expression, env:)
358
+ pointer_type = const_pointer_to(target_type)
359
+
360
+ if pointer_type?(actual_type) || ref_type?(actual_type)
361
+ return cast_expression(lowered_expression, pointer_type)
362
+ end
363
+
364
+ return cast_expression(lowered_expression.operand, pointer_type) if lowered_expression.is_a?(IR::Unary) && lowered_expression.operator == "*"
365
+
366
+ IR::AddressOf.new(expression: lowered_expression, type: pointer_type)
367
+ end
368
+
369
+ def lower_call_arguments(arguments, callee_type, env:)
370
+ arguments.map.with_index do |argument, index|
371
+ parameter = index < callee_type.params.length ? callee_type.params[index] : nil
372
+ expected_type = parameter&.type
373
+ external_call = callee_type.respond_to?(:external) && callee_type.external && !expected_type.nil?
374
+ if external_call && parameter && %i[out inout].include?(parameter.passing_mode)
375
+ next lower_foreign_pointer_argument_value(parameter, argument, env:)
376
+ end
377
+
378
+ lower_contextual_expression(
379
+ argument.value,
380
+ env:,
381
+ expected_type:,
382
+ external_numeric: external_call,
383
+ contextual_int_to_float: expected_type && contextual_int_to_float_target?(expected_type) && !external_call,
384
+ )
385
+ end
386
+ end
387
+
388
+ def implicit_ref_argument_bridge?(expression, expected_type, env:)
389
+ return false unless ref_type?(expected_type)
390
+
391
+ actual_type = infer_expression_type(expression, env:)
392
+ actual_type == referenced_type(expected_type) && addressable_storage_expression?(expression)
393
+ end
394
+
395
+ def task_expression_root_proc_bridge?(expression, expected_type, env:)
396
+ return false unless task_root_proc_type?(expected_type)
397
+
398
+ actual_type = infer_expression_type(expression, env:)
399
+ actual_type.is_a?(Types::Task) && actual_type == expected_type.return_type
400
+ end
401
+
402
+ def wrap_task_expression_in_root_proc(expression, env:)
403
+ task_type = infer_expression_type(expression, env:)
404
+ AST::ProcExpr.new(
405
+ params: [],
406
+ return_type: ast_type_ref_for(task_type),
407
+ body: [AST::ReturnStmt.new(value: expression)],
408
+ )
409
+ end
410
+
411
+ def wrap_expression_in_ref_of(expression)
412
+ AST::Call.new(
413
+ callee: AST::Identifier.new(name: "ref_of"),
414
+ arguments: [AST::Argument.new(name: nil, value: expression)],
415
+ )
416
+ end
417
+
418
+ def foreign_call_info(expression, env)
419
+ call = expression if expression.is_a?(AST::Call)
420
+ return unless call
421
+
422
+ kind, _, _, _, binding = resolve_callee(call.callee, env, arguments: call.arguments)
423
+ return unless kind == :function && binding && foreign_function_binding?(binding)
424
+
425
+ {
426
+ call:,
427
+ binding:,
428
+ }
429
+ end
430
+
431
+ def foreign_call_consumes_binding?(binding)
432
+ binding.type.params.any? { |parameter| parameter.passing_mode == :consuming }
433
+ end
434
+
435
+ def lower_foreign_call_components(foreign_call, env:, expected_type:, statement_position:)
436
+ call = foreign_call.fetch(:call)
437
+ binding = foreign_call.fetch(:binding)
438
+ raise LoweringError, "consuming foreign calls must be top-level expression statements" if foreign_call_consumes_binding?(binding) && !statement_position
439
+
440
+ previous_type_substitutions = @ctx.current_type_substitutions
441
+ @ctx.current_type_substitutions = binding.type_substitutions
442
+
443
+ owner_analysis = analysis_for_module(binding.owner.module_name)
444
+ mapping_expression = foreign_mapping_expression(binding.ast)
445
+ reference_counts = foreign_mapping_reference_counts(mapping_expression)
446
+ mapping_env = duplicate_env(env)
447
+ lowered = []
448
+ release_assignments = consuming_foreign_release_assignments(foreign_call, env:)
449
+ cleanup_statements = []
450
+
451
+ replacements = bind_foreign_mapping_arguments(binding, call.arguments, mapping_env, lowered, env:, reference_counts:, cleanup: cleanup_statements)
452
+
453
+ call_type = binding.type.return_type
454
+ lowered_call = lower_inline_foreign_mapping_expression(
455
+ mapping_expression,
456
+ mapping_env:,
457
+ replacements:,
458
+ owner_analysis:,
459
+ expected_type: expected_type || call_type,
460
+ )
461
+ lowered_call = append_variadic_foreign_call_arguments(
462
+ lowered_call,
463
+ call.arguments,
464
+ binding.type,
465
+ env:,
466
+ lowered:,
467
+ cleanup: cleanup_statements,
468
+ )
469
+
470
+ [lowered, lowered_call, call_type, release_assignments, cleanup_statements]
471
+ ensure
472
+ @ctx.current_type_substitutions = previous_type_substitutions
473
+ end
474
+
475
+ def lower_foreign_call_statement(foreign_call, env:, expected_type:, statement_position:, discard_result: false)
476
+ lowered, lowered_call, call_type, release_assignments, cleanup_statements = lower_foreign_call_components(
477
+ foreign_call,
478
+ env:,
479
+ expected_type:,
480
+ statement_position:,
481
+ )
482
+
483
+ if call_type == @ctx.types.fetch("void")
484
+ lowered << IR::ExpressionStmt.new(expression: lowered_call)
485
+ lowered.concat(release_assignments)
486
+ lowered.concat(cleanup_statements)
487
+ return [lowered, nil]
488
+ end
489
+
490
+ raise LoweringError, "consuming foreign calls must return void" unless release_assignments.empty?
491
+
492
+ if discard_result
493
+ lowered << IR::ExpressionStmt.new(expression: lowered_call)
494
+ lowered.concat(cleanup_statements)
495
+ return [lowered, nil]
496
+ end
497
+
498
+ unless cleanup_statements.empty?
499
+ result_name = fresh_c_temp_name(env, "foreign_result")
500
+ lowered << IR::LocalDecl.new(name: result_name, linkage_name: result_name, type: call_type, value: lowered_call)
501
+ lowered.concat(cleanup_statements)
502
+ return [lowered, IR::Name.new(name: result_name, type: call_type, pointer: false)]
503
+ end
504
+
505
+ [lowered, lowered_call]
506
+ end
507
+
508
+ def consuming_foreign_release_assignments(foreign_call, env:)
509
+ consuming_foreign_release_bindings(foreign_call, env:).map do |binding|
510
+ IR::Assignment.new(
511
+ target: IR::Name.new(name: binding[:linkage_name], type: binding[:storage_type], pointer: binding[:pointer]),
512
+ operator: "=",
513
+ value: IR::NullLiteral.new(type: binding[:storage_type]),
514
+ )
515
+ end
516
+ end
517
+
518
+ def consuming_foreign_call_refinements(foreign_call, env)
519
+ consuming_foreign_release_bindings(foreign_call, env:).each_with_object({}) do |binding, refinements|
520
+ refinements[binding[:name]] = null_type
521
+ end
522
+ end
523
+
524
+ def consuming_foreign_release_bindings(foreign_call, env:)
525
+ binding = foreign_call.fetch(:binding)
526
+ call = foreign_call.fetch(:call)
527
+
528
+ binding.type.params.each_with_index.filter_map do |parameter, index|
529
+ next unless parameter.passing_mode == :consuming
530
+
531
+ argument = call.arguments.fetch(index)
532
+ unless argument.value.is_a?(AST::Identifier)
533
+ raise LoweringError, "consuming foreign calls require bare nullable local or parameter bindings"
534
+ end
535
+
536
+ lowered_binding = lookup_value(argument.value.name, env)
537
+ unless lowered_binding && lowered_binding[:storage_type].is_a?(Types::Nullable) && lowered_binding[:storage_type].base == parameter.type
538
+ raise LoweringError, "consuming foreign calls require bare nullable local or parameter bindings"
539
+ end
540
+
541
+ lowered_binding.merge(name: argument.value.name)
542
+ end
543
+ end
544
+
545
+ def bind_foreign_mapping_arguments(binding, arguments, mapping_env, lowered, env:, reference_counts:, cleanup:)
546
+ replacements = {}
547
+ entries = binding.ast.params.each_with_index.map do |param_ast, index|
548
+ parameter = binding.type.params.fetch(index)
549
+ public_alias = param_ast.boundary_type ? foreign_mapping_public_alias_name(param_ast.name) : nil
550
+ {
551
+ argument: arguments.fetch(index),
552
+ param_ast:,
553
+ parameter:,
554
+ temp_type: parameter.boundary_type || parameter.type,
555
+ public_alias:,
556
+ public_reference_count: public_alias ? reference_counts.fetch(public_alias, 0) : 0,
557
+ reference_count: reference_counts.fetch(param_ast.name, 0),
558
+ lowered_value: nil,
559
+ }
560
+ end
561
+
562
+ entries.each do |entry|
563
+ next unless entry[:public_reference_count].positive?
564
+
565
+ public_value = lower_contextual_expression(entry[:argument].value, env:, expected_type: entry[:parameter].type)
566
+ if public_value.is_a?(IR::Name)
567
+ current_actual_scope(mapping_env[:scopes])[entry[:public_alias]] = local_binding(
568
+ type: entry[:parameter].type,
569
+ linkage_name: public_value.name,
570
+ mutable: false,
571
+ pointer: public_value.pointer,
572
+ )
573
+ replacements[entry[:public_alias]] = public_value
574
+ next
575
+ end
576
+
577
+ public_temp_name = fresh_c_temp_name(env, "foreign_arg_public")
578
+ lowered << IR::LocalDecl.new(
579
+ name: public_temp_name,
580
+ linkage_name: public_temp_name,
581
+ type: entry[:parameter].type,
582
+ value: public_value,
583
+ )
584
+ current_actual_scope(mapping_env[:scopes])[entry[:public_alias]] = local_binding(
585
+ type: entry[:parameter].type,
586
+ linkage_name: public_temp_name,
587
+ mutable: false,
588
+ pointer: false,
589
+ )
590
+ replacements[entry[:public_alias]] = IR::Name.new(name: public_temp_name, type: entry[:parameter].type, pointer: false)
591
+ end
592
+
593
+ entries.each do |entry|
594
+ next unless entry[:reference_count].positive?
595
+
596
+ source_argument = if entry[:public_reference_count].positive?
597
+ AST::Argument.new(name: nil, value: AST::Identifier.new(name: entry[:public_alias]))
598
+ else
599
+ entry[:argument]
600
+ end
601
+ source_env = entry[:public_reference_count].positive? ? mapping_env : env
602
+ source_argument = prepare_foreign_in_argument(entry[:parameter], source_argument, source_env:, lowered:, env:)
603
+ entry[:lowered_value] = if automatic_foreign_cstr_list_temp_needed?(entry[:parameter], source_argument.value, env: source_env)
604
+ lower_foreign_cstr_list_argument_value(entry[:parameter], source_argument.value, env: source_env, lowered:, cleanup:)
605
+ else
606
+ lower_foreign_argument_value(entry[:parameter], source_argument, env: source_env)
607
+ end
608
+ end
609
+
610
+ inline_direct_call_names = inlineable_single_direct_call_names(entries)
611
+
612
+ entries.each do |entry|
613
+ next unless entry[:reference_count].positive?
614
+
615
+ param_ast = entry[:param_ast]
616
+ temp_type = entry[:temp_type]
617
+ lowered_value = entry[:lowered_value]
618
+
619
+ if !inline_direct_call_names.include?(param_ast.name) && foreign_argument_needs_temporary_binding?(lowered_value, reference_count: entry[:reference_count])
620
+ temp_name = fresh_c_temp_name(env, "foreign_arg")
621
+ lowered << IR::LocalDecl.new(
622
+ name: temp_name,
623
+ linkage_name: temp_name,
624
+ type: temp_type,
625
+ value: lowered_value,
626
+ )
627
+ current_actual_scope(mapping_env[:scopes])[param_ast.name] = local_binding(type: temp_type, linkage_name: temp_name, mutable: false, pointer: false)
628
+ replacements[param_ast.name] = IR::Name.new(name: temp_name, type: temp_type, pointer: false)
629
+ if temporary_foreign_cstr_expression?(lowered_value)
630
+ cleanup << IR::ExpressionStmt.new(
631
+ expression: IR::Call.new(
632
+ callee: "mt_free_foreign_cstr_temp",
633
+ arguments: [IR::Name.new(name: temp_name, type: temp_type, pointer: false)],
634
+ type: @ctx.types.fetch("void"),
635
+ ),
636
+ )
637
+ end
638
+ else
639
+ current_actual_scope(mapping_env[:scopes])[param_ast.name] = local_binding(type: temp_type, linkage_name: param_ast.name, mutable: false, pointer: false)
640
+ replacements[param_ast.name] = lowered_value
641
+ end
642
+ end
643
+
644
+ replacements
645
+ end
646
+
647
+ def inlineable_single_direct_call_names(entries)
648
+ blocked_entries = entries.select do |entry|
649
+ next false unless entry[:reference_count].positive?
650
+
651
+ entry[:reference_count] > 1 || !inlineable_foreign_argument_expression?(entry[:lowered_value])
652
+ end
653
+ return [] unless blocked_entries.length == 1
654
+
655
+ blocked_entry = blocked_entries.first
656
+ return [] unless blocked_entry[:reference_count] == 1 && blocked_entry[:lowered_value].is_a?(IR::Call)
657
+ return [] if temporary_foreign_cstr_expression?(blocked_entry[:lowered_value])
658
+
659
+ [blocked_entry[:param_ast].name]
660
+ end
661
+
662
+ def foreign_argument_expression(argument)
663
+ if argument.value.is_a?(AST::UnaryOp) && ["out", "in", "inout"].include?(argument.value.operator)
664
+ argument.value.operand
665
+ else
666
+ argument.value
667
+ end
668
+ end
669
+
670
+ def lower_foreign_argument_value(parameter, argument, env:)
671
+ case parameter.passing_mode
672
+ when :plain, :consuming
673
+ if parameter.boundary_type.nil? || parameter.boundary_type == parameter.type
674
+ expected = parameter.passing_mode == :consuming ? Types::Registry.nullable(parameter.type) : parameter.type
675
+ lower_contextual_expression(argument.value, env:, expected_type: expected)
676
+ elsif parameter.boundary_type == @ctx.types.fetch("cstr") && parameter.type == @ctx.types.fetch("str")
677
+ if argument.value.is_a?(AST::StringLiteral) && !argument.value.cstring
678
+ return IR::StringLiteral.new(value: argument.value.value, type: parameter.boundary_type, cstring: true)
679
+ end
680
+
681
+ actual_type = infer_expression_type(argument.value, env:)
682
+ if actual_type == @ctx.types.fetch("cstr")
683
+ return lower_expression(argument.value, env:, expected_type: parameter.boundary_type)
684
+ end
685
+
686
+ if cstr_backed_expression?(argument.value, env)
687
+ lowered_value = lower_contextual_expression(argument.value, env:, expected_type: parameter.type)
688
+ data_expression = IR::Member.new(receiver: lowered_value, member: "data", type: pointer_to(@ctx.types.fetch("char")))
689
+ converted = foreign_identity_projection_expression(data_expression, parameter.boundary_type)
690
+ return converted if converted
691
+ end
692
+
693
+ IR::Call.new(
694
+ callee: "mt_foreign_str_to_cstr_temp",
695
+ arguments: [lower_contextual_expression(argument.value, env:, expected_type: parameter.type)],
696
+ type: parameter.boundary_type,
697
+ )
698
+ elsif foreign_span_boundary_compatible?(parameter.type, parameter.boundary_type)
699
+ lower_foreign_span_argument_value(parameter, argument, env:)
700
+ elsif foreign_char_pointer_buffer_boundary_compatible?(parameter.type, parameter.boundary_type)
701
+ lower_foreign_char_pointer_buffer_argument_value(parameter, argument, env:)
702
+ else
703
+ lowered_value = lower_contextual_expression(argument.value, env:, expected_type: parameter.type)
704
+ converted = foreign_identity_projection_expression(lowered_value, parameter.boundary_type)
705
+ return converted if converted
706
+
707
+ raise LoweringError, "unsupported foreign boundary mapping #{parameter.type} as #{parameter.boundary_type}"
708
+ end
709
+ when :in
710
+ lower_foreign_in_argument_value(parameter, argument, env:)
711
+ when :out, :inout
712
+ lower_foreign_pointer_argument_value(parameter, argument, env:)
713
+ else
714
+ raise LoweringError, "unsupported foreign passing mode #{parameter.passing_mode}"
715
+ end
716
+ end
717
+
718
+ def lower_foreign_span_argument_value(parameter, argument, env:)
719
+ public_type = parameter.type
720
+ boundary_type = parameter.boundary_type
721
+ lowered_value = lower_contextual_expression(argument.value, env:, expected_type: public_type)
722
+ return lowered_value if public_type == boundary_type
723
+
724
+ public_element_type = public_type.element_type
725
+ boundary_element_type = boundary_type.element_type
726
+
727
+ data_expression = IR::Member.new(receiver: lowered_value, member: "data", type: pointer_to(public_element_type))
728
+ converted_data = foreign_identity_projection_expression(data_expression, pointer_to(boundary_element_type))
729
+ raise LoweringError, "unsupported foreign boundary mapping #{public_type} as #{boundary_type}" unless converted_data
730
+
731
+ len_expression = IR::Member.new(receiver: lowered_value, member: "len", type: @ctx.types.fetch("ptr_uint"))
732
+ IR::AggregateLiteral.new(
733
+ type: boundary_type,
734
+ fields: [
735
+ IR::AggregateField.new(name: "data", value: converted_data),
736
+ IR::AggregateField.new(name: "len", value: len_expression),
737
+ ],
738
+ )
739
+ end
740
+
741
+ def lower_foreign_pointer_argument_value(parameter, argument, env:)
742
+ slot_type = foreign_slot_boundary_value_type(parameter.type)
743
+ operand = foreign_argument_expression(argument)
744
+ address = lower_addr_expression(operand, env:, target_type: pointer_to(slot_type))
745
+
746
+ converted = foreign_identity_projection_expression(address, parameter.boundary_type)
747
+ return converted if converted
748
+
749
+ raise LoweringError, "unsupported foreign pointer boundary mapping #{parameter.type} as #{parameter.boundary_type}"
750
+ end
751
+
752
+ def foreign_slot_boundary_value_type(type)
753
+ if type.is_a?(Types::Nullable) && pointer_like_type?(type.base)
754
+ return type.base
755
+ end
756
+
757
+ type
758
+ end
759
+
760
+ def prepare_foreign_in_argument(parameter, argument, source_env:, lowered:, env:)
761
+ return argument unless parameter.passing_mode == :in
762
+
763
+ operand = foreign_argument_expression(argument)
764
+ return argument if addressable_storage_expression?(operand)
765
+
766
+ temp_name = fresh_c_temp_name(env, "foreign_in")
767
+ lowered << IR::LocalDecl.new(
768
+ name: temp_name,
769
+ linkage_name: temp_name,
770
+ type: parameter.type,
771
+ value: lower_contextual_expression(operand, env: source_env, expected_type: parameter.type),
772
+ )
773
+ current_actual_scope(source_env[:scopes])[temp_name] = local_binding(type: parameter.type, linkage_name: temp_name, mutable: false, pointer: false)
774
+
775
+ AST::Argument.new(
776
+ name: argument.name,
777
+ value: AST::Identifier.new(name: temp_name),
778
+ )
779
+ end
780
+
781
+ def lower_foreign_in_argument_value(parameter, argument, env:)
782
+ address = lower_addr_expression(
783
+ foreign_argument_expression(argument),
784
+ env:,
785
+ target_type: const_pointer_to(parameter.type),
786
+ )
787
+
788
+ converted = foreign_identity_projection_expression(address, parameter.boundary_type)
789
+ return converted if converted
790
+
791
+ raise LoweringError, "unsupported foreign in boundary mapping #{parameter.type} as #{parameter.boundary_type}"
792
+ end
793
+
794
+ def lower_foreign_char_pointer_buffer_argument_value(parameter, argument, env:)
795
+ public_type = parameter.type
796
+
797
+ if char_array_text_type?(public_type)
798
+ return lower_char_array_data_pointer(argument.value, env:)
799
+ end
800
+
801
+ if str_buffer_type?(public_type)
802
+ return IR::Call.new(
803
+ callee: "mt_str_buffer_prepare_write",
804
+ arguments: [
805
+ lower_str_buffer_data_pointer(argument.value, env:),
806
+ IR::IntegerLiteral.new(value: str_buffer_capacity(public_type), type: @ctx.types.fetch("ptr_uint")),
807
+ lower_str_buffer_dirty_pointer(argument.value, env:),
808
+ ],
809
+ type: parameter.boundary_type,
810
+ )
811
+ end
812
+
813
+ lowered_value = lower_contextual_expression(argument.value, env:, expected_type: public_type)
814
+ return IR::Member.new(receiver: lowered_value, member: "data", type: parameter.boundary_type) if public_type.is_a?(Types::Span) && public_type.element_type == @ctx.types.fetch("char")
815
+
816
+ converted = foreign_identity_projection_expression(lowered_value, parameter.boundary_type)
817
+ return converted if converted
818
+
819
+ raise LoweringError, "unsupported foreign boundary mapping #{public_type} as #{parameter.boundary_type}"
820
+ end
821
+
822
+ def lower_foreign_call_inline(expression, binding, env:, type:)
823
+ previous_type_substitutions = @ctx.current_type_substitutions
824
+ @ctx.current_type_substitutions = binding.type_substitutions
825
+
826
+ owner_analysis = analysis_for_module(binding.owner.module_name)
827
+ mapping_expression = foreign_mapping_expression(binding.ast)
828
+ reference_counts = foreign_mapping_reference_counts(mapping_expression)
829
+ mapping_env = duplicate_env(env)
830
+
831
+ binding.ast.params.each_with_index do |param_ast, index|
832
+ public_alias = param_ast.boundary_type ? foreign_mapping_public_alias_name(param_ast.name) : nil
833
+ total_references = reference_counts.fetch(param_ast.name, 0)
834
+ total_references += reference_counts.fetch(public_alias, 0) if public_alias
835
+ next unless total_references > 1
836
+ next if duplicable_foreign_argument_expression?(expression.arguments.fetch(index).value)
837
+
838
+ raise LoweringError, "foreign call #{binding.name} cannot be used inline because #{param_ast.name} is referenced multiple times in its mapping; use it as a statement, local initializer, assignment, or return expression"
839
+ end
840
+
841
+ binding.ast.params.each_with_index do |param_ast, index|
842
+ parameter = binding.type.params.fetch(index)
843
+ next unless automatic_foreign_cstr_temp_needed?(parameter, expression.arguments.fetch(index).value, env:) ||
844
+ automatic_foreign_cstr_list_temp_needed?(parameter, expression.arguments.fetch(index).value, env:)
845
+
846
+ raise LoweringError, "foreign call #{binding.name} cannot be used inline because #{param_ast.name} needs temporary foreign text storage; use it as a statement, local initializer, assignment, or return expression"
847
+ end
848
+
849
+ expression.arguments.drop(binding.type.params.length).each do |argument|
850
+ next unless automatic_variadic_foreign_cstr_temp_needed?(argument.value, env:)
851
+
852
+ raise LoweringError, "foreign call #{binding.name} cannot be used inline because a variadic argument needs temporary foreign text storage; use it as a statement, local initializer, assignment, or return expression"
853
+ end
854
+
855
+ binding.ast.params.each_with_index do |param_ast, index|
856
+ parameter = binding.type.params.fetch(index)
857
+ argument = expression.arguments.fetch(index)
858
+ next unless parameter.passing_mode == :in
859
+ next if addressable_storage_expression?(foreign_argument_expression(argument))
860
+
861
+ raise LoweringError, "foreign call #{binding.name} cannot be used inline because #{param_ast.name} needs temporary in storage; use it as a statement, local initializer, assignment, or return expression"
862
+ end
863
+
864
+ replacements = {}
865
+ binding.ast.params.each_with_index do |param_ast, index|
866
+ parameter = binding.type.params.fetch(index)
867
+ temp_type = parameter.boundary_type || parameter.type
868
+ public_alias = param_ast.boundary_type ? foreign_mapping_public_alias_name(param_ast.name) : nil
869
+
870
+ if reference_counts.fetch(param_ast.name, 0).positive?
871
+ current_actual_scope(mapping_env[:scopes])[param_ast.name] = local_binding(type: temp_type, linkage_name: param_ast.name, mutable: false, pointer: false)
872
+ replacements[param_ast.name] = lower_foreign_argument_value(parameter, expression.arguments.fetch(index), env:)
873
+ end
874
+
875
+ next unless public_alias && reference_counts.fetch(public_alias, 0).positive?
876
+
877
+ current_actual_scope(mapping_env[:scopes])[public_alias] = local_binding(type: parameter.type, linkage_name: public_alias, mutable: false, pointer: false)
878
+ replacements[public_alias] = lower_contextual_expression(expression.arguments.fetch(index).value, env:, expected_type: parameter.type)
879
+ end
880
+
881
+ lowered_expression = lower_inline_foreign_mapping_expression(
882
+ mapping_expression,
883
+ mapping_env:,
884
+ replacements:,
885
+ owner_analysis:,
886
+ expected_type: type,
887
+ )
888
+ lowered_expression = append_variadic_foreign_call_arguments(
889
+ lowered_expression,
890
+ expression.arguments,
891
+ binding.type,
892
+ env:,
893
+ )
894
+
895
+ converted = foreign_identity_projection_expression(lowered_expression, type)
896
+ return converted if converted
897
+
898
+ lowered_expression
899
+ ensure
900
+ @ctx.current_type_substitutions = previous_type_substitutions
901
+ end
902
+
903
+ def append_variadic_foreign_call_arguments(lowered_expression, arguments, function_type, env:, lowered: nil, cleanup: nil)
904
+ return lowered_expression unless function_type.variadic
905
+
906
+ extra_arguments = arguments.drop(function_type.params.length)
907
+ return lowered_expression if extra_arguments.empty?
908
+ return lowered_expression unless lowered_expression.is_a?(IR::Call)
909
+
910
+ IR::Call.new(
911
+ callee: lowered_expression.callee,
912
+ arguments: lowered_expression.arguments + extra_arguments.map { |argument| lower_variadic_foreign_argument(argument, env:, lowered:, cleanup:) },
913
+ type: lowered_expression.type,
914
+ )
915
+ end
916
+
917
+ def lower_variadic_foreign_argument(argument, env:, lowered:, cleanup:)
918
+ actual_type = infer_expression_type(argument.value, env:)
919
+ return lower_contextual_expression(argument.value, env:, expected_type: nil) unless actual_type == @ctx.types.fetch("str")
920
+
921
+ lowered_argument = lower_foreign_argument_value(
922
+ Types::Registry.parameter("__mt_variadic", actual_type, passing_mode: :plain, boundary_type: @ctx.types.fetch("cstr")),
923
+ argument,
924
+ env:,
925
+ )
926
+ return lowered_argument unless temporary_foreign_cstr_expression?(lowered_argument)
927
+
928
+ raise LoweringError, "foreign variadic call cannot be used inline because an extra argument needs temporary foreign text storage; use it as a statement, local initializer, assignment, or return expression" unless lowered && cleanup
929
+
930
+ temp_name = fresh_c_temp_name(env, "foreign_arg")
931
+ lowered << IR::LocalDecl.new(
932
+ name: temp_name,
933
+ linkage_name: temp_name,
934
+ type: @ctx.types.fetch("cstr"),
935
+ value: lowered_argument,
936
+ )
937
+ cleanup << IR::ExpressionStmt.new(
938
+ expression: IR::Call.new(
939
+ callee: "mt_free_foreign_cstr_temp",
940
+ arguments: [IR::Name.new(name: temp_name, type: @ctx.types.fetch("cstr"), pointer: false)],
941
+ type: @ctx.types.fetch("void"),
942
+ ),
943
+ )
944
+ IR::Name.new(name: temp_name, type: @ctx.types.fetch("cstr"), pointer: false)
945
+ end
946
+
947
+ def lower_inline_foreign_mapping_expression(expression, mapping_env:, replacements:, owner_analysis:, expected_type: nil)
948
+ unless foreign_mapping_uses_inline_replacement?(expression, replacements)
949
+ return with_analysis_context(owner_analysis) do
950
+ lower_expression(expression, env: mapping_env, expected_type:)
951
+ end
952
+ end
953
+
954
+ type = with_analysis_context(owner_analysis) do
955
+ infer_expression_type(expression, env: mapping_env, expected_type:)
956
+ end
957
+
958
+ case expression
959
+ when AST::Identifier
960
+ replacements.fetch(expression.name)
961
+ when AST::MemberAccess
962
+ receiver_type = with_analysis_context(owner_analysis) do
963
+ infer_expression_type(expression.receiver, env: mapping_env)
964
+ end
965
+ receiver = lower_inline_foreign_mapping_expression(
966
+ expression.receiver,
967
+ mapping_env:,
968
+ replacements:,
969
+ owner_analysis:,
970
+ )
971
+ IR::Member.new(receiver:, member: member_c_name(receiver_type, expression.member), type:)
972
+ when AST::IndexAccess
973
+ receiver_type = with_analysis_context(owner_analysis) do
974
+ infer_expression_type(expression.receiver, env: mapping_env)
975
+ end
976
+ receiver = lower_inline_foreign_mapping_expression(
977
+ expression.receiver,
978
+ mapping_env:,
979
+ replacements:,
980
+ owner_analysis:,
981
+ )
982
+ index = lower_inline_foreign_mapping_expression(
983
+ expression.index,
984
+ mapping_env:,
985
+ replacements:,
986
+ owner_analysis:,
987
+ )
988
+ if array_type?(receiver_type) && addressable_storage_expression?(expression.receiver)
989
+ IR::CheckedIndex.new(receiver:, index:, receiver_type:, type:)
990
+ elsif receiver_type.is_a?(Types::Span)
991
+ IR::CheckedSpanIndex.new(receiver:, index:, receiver_type:, type:)
992
+ else
993
+ IR::Index.new(receiver:, index:, type:)
994
+ end
995
+ when AST::Call
996
+ lower_inline_foreign_mapping_call(expression, mapping_env:, replacements:, owner_analysis:, type:)
997
+ when AST::UnaryOp
998
+ IR::Unary.new(
999
+ operator: expression.operator,
1000
+ operand: lower_inline_foreign_mapping_expression(
1001
+ expression.operand,
1002
+ mapping_env:,
1003
+ replacements:,
1004
+ owner_analysis:,
1005
+ expected_type: type,
1006
+ ),
1007
+ type:,
1008
+ )
1009
+ when AST::BinaryOp
1010
+ left_type, right_type = with_analysis_context(owner_analysis) do
1011
+ infer_binary_operand_types(expression, env: mapping_env, expected_type: type)
1012
+ end
1013
+ operand_type = promoted_binary_operand_type(expression.operator, left_type, right_type)
1014
+ left = lower_inline_foreign_mapping_expression(
1015
+ expression.left,
1016
+ mapping_env:,
1017
+ replacements:,
1018
+ owner_analysis:,
1019
+ expected_type: operand_type || type,
1020
+ )
1021
+ right = lower_inline_foreign_mapping_expression(
1022
+ expression.right,
1023
+ mapping_env:,
1024
+ replacements:,
1025
+ owner_analysis:,
1026
+ expected_type: operand_type || left.type,
1027
+ )
1028
+ left = cast_expression(left, operand_type) if operand_type
1029
+ right = cast_expression(right, operand_type) if operand_type
1030
+ IR::Binary.new(operator: expression.operator, left:, right:, type:)
1031
+ when AST::IfExpr
1032
+ IR::Conditional.new(
1033
+ condition: lower_inline_foreign_mapping_expression(
1034
+ expression.condition,
1035
+ mapping_env:,
1036
+ replacements:,
1037
+ owner_analysis:,
1038
+ expected_type: @ctx.types.fetch("bool"),
1039
+ ),
1040
+ then_expression: lower_inline_foreign_mapping_expression(
1041
+ expression.then_expression,
1042
+ mapping_env:,
1043
+ replacements:,
1044
+ owner_analysis:,
1045
+ expected_type: type,
1046
+ ),
1047
+ else_expression: lower_inline_foreign_mapping_expression(
1048
+ expression.else_expression,
1049
+ mapping_env:,
1050
+ replacements:,
1051
+ owner_analysis:,
1052
+ expected_type: type,
1053
+ ),
1054
+ type:,
1055
+ )
1056
+ when AST::PrefixCast
1057
+ lowered_expr = lower_inline_foreign_mapping_expression(
1058
+ expression.expression,
1059
+ mapping_env:,
1060
+ replacements:,
1061
+ owner_analysis:,
1062
+ )
1063
+ IR::Cast.new(target_type: type, expression: lowered_expr, type:)
1064
+ else
1065
+ with_analysis_context(owner_analysis) do
1066
+ lower_expression(expression, env: mapping_env, expected_type:)
1067
+ end
1068
+ end
1069
+ end
1070
+
1071
+ def lower_inline_foreign_mapping_call(expression, mapping_env:, replacements:, owner_analysis:, type:)
1072
+ kind, callee_name, receiver, callee_type, callee_binding = with_analysis_context(owner_analysis) do
1073
+ resolve_callee(expression.callee, mapping_env, arguments: expression.arguments)
1074
+ end
1075
+
1076
+ case kind
1077
+ when :function
1078
+ raise LoweringError, "consuming foreign calls must be top-level expression statements" if callee_binding && foreign_function_binding?(callee_binding) && foreign_call_consumes_binding?(callee_binding)
1079
+
1080
+ arguments = expression.arguments.map.with_index do |argument, index|
1081
+ expected_arg_type = index < callee_type.params.length ? callee_type.params[index].type : nil
1082
+ lower_inline_foreign_mapping_expression(
1083
+ argument.value,
1084
+ mapping_env:,
1085
+ replacements:,
1086
+ owner_analysis:,
1087
+ expected_type: expected_arg_type,
1088
+ )
1089
+ end
1090
+ IR::Call.new(callee: callee_name, arguments:, type:)
1091
+ when :reinterpret
1092
+ argument = expression.arguments.fetch(0)
1093
+ source_type = with_analysis_context(owner_analysis) do
1094
+ infer_expression_type(argument.value, env: mapping_env)
1095
+ end
1096
+ IR::ReinterpretExpr.new(
1097
+ target_type: type,
1098
+ source_type:,
1099
+ expression: lower_inline_foreign_mapping_expression(
1100
+ argument.value,
1101
+ mapping_env:,
1102
+ replacements:,
1103
+ owner_analysis:,
1104
+ expected_type: source_type,
1105
+ ),
1106
+ type:,
1107
+ )
1108
+ when :hash
1109
+ resolution = with_analysis_context(owner_analysis) do
1110
+ resolve_hash_specialization(expression.callee, env: mapping_env)
1111
+ end
1112
+ argument = expression.arguments.fetch(0)
1113
+ IR::Call.new(
1114
+ callee: resolution.callee_name,
1115
+ arguments: [
1116
+ lower_inline_hash_operation_argument(
1117
+ argument.value,
1118
+ mapping_env:,
1119
+ replacements:,
1120
+ owner_analysis:,
1121
+ target_type: resolution.target_type,
1122
+ ),
1123
+ ],
1124
+ type:,
1125
+ )
1126
+ when :equal
1127
+ resolution = with_analysis_context(owner_analysis) do
1128
+ resolve_equal_specialization(expression.callee, env: mapping_env)
1129
+ end
1130
+ left = expression.arguments.fetch(0)
1131
+ right = expression.arguments.fetch(1)
1132
+ IR::Call.new(
1133
+ callee: resolution.callee_name,
1134
+ arguments: [
1135
+ lower_inline_hash_operation_argument(
1136
+ left.value,
1137
+ mapping_env:,
1138
+ replacements:,
1139
+ owner_analysis:,
1140
+ target_type: resolution.target_type,
1141
+ ),
1142
+ lower_inline_hash_operation_argument(
1143
+ right.value,
1144
+ mapping_env:,
1145
+ replacements:,
1146
+ owner_analysis:,
1147
+ target_type: resolution.target_type,
1148
+ ),
1149
+ ],
1150
+ type:,
1151
+ )
1152
+ when :order
1153
+ resolution = with_analysis_context(owner_analysis) do
1154
+ resolve_order_specialization(expression.callee, env: mapping_env)
1155
+ end
1156
+ left = expression.arguments.fetch(0)
1157
+ right = expression.arguments.fetch(1)
1158
+ IR::Call.new(
1159
+ callee: resolution.callee_name,
1160
+ arguments: [
1161
+ lower_inline_hash_operation_argument(
1162
+ left.value,
1163
+ mapping_env:,
1164
+ replacements:,
1165
+ owner_analysis:,
1166
+ target_type: resolution.target_type,
1167
+ ),
1168
+ lower_inline_hash_operation_argument(
1169
+ right.value,
1170
+ mapping_env:,
1171
+ replacements:,
1172
+ owner_analysis:,
1173
+ target_type: resolution.target_type,
1174
+ ),
1175
+ ],
1176
+ type:,
1177
+ )
1178
+ when :zero
1179
+ IR::ZeroInit.new(type:)
1180
+ when :ref_of
1181
+ argument = expression.arguments.fetch(0)
1182
+ lowered_argument = lower_inline_foreign_mapping_expression(
1183
+ argument.value,
1184
+ mapping_env:,
1185
+ replacements:,
1186
+ owner_analysis:,
1187
+ )
1188
+ if lowered_argument.is_a?(IR::Name) && lowered_argument.pointer
1189
+ cast_expression(lowered_argument, type)
1190
+ elsif lowered_argument.is_a?(IR::Unary) && lowered_argument.operator == "*"
1191
+ cast_expression(lowered_argument.operand, type)
1192
+ else
1193
+ IR::AddressOf.new(expression: lowered_argument, type:)
1194
+ end
1195
+ when :const_ptr_of
1196
+ argument = expression.arguments.fetch(0)
1197
+ lowered_argument = lower_inline_foreign_mapping_expression(
1198
+ argument.value,
1199
+ mapping_env:,
1200
+ replacements:,
1201
+ owner_analysis:,
1202
+ )
1203
+ if lowered_argument.is_a?(IR::Name) && lowered_argument.pointer
1204
+ cast_expression(lowered_argument, type)
1205
+ elsif lowered_argument.is_a?(IR::Unary) && lowered_argument.operator == "*"
1206
+ cast_expression(lowered_argument.operand, type)
1207
+ else
1208
+ IR::AddressOf.new(expression: lowered_argument, type:)
1209
+ end
1210
+ when :read
1211
+ argument = expression.arguments.fetch(0)
1212
+ IR::Unary.new(
1213
+ operator: "*",
1214
+ operand: lower_inline_foreign_mapping_expression(
1215
+ argument.value,
1216
+ mapping_env:,
1217
+ replacements:,
1218
+ owner_analysis:,
1219
+ ),
1220
+ type:,
1221
+ )
1222
+ when :str_buffer_capacity
1223
+ receiver_type = with_analysis_context(owner_analysis) do
1224
+ infer_expression_type(receiver, env: mapping_env)
1225
+ end
1226
+ IR::IntegerLiteral.new(value: str_buffer_capacity(receiver_type), type:)
1227
+ when :ptr_of
1228
+ argument = expression.arguments.fetch(0)
1229
+ argument_type = with_analysis_context(owner_analysis) do
1230
+ infer_expression_type(argument.value, env: mapping_env)
1231
+ end
1232
+ if ref_type?(argument_type)
1233
+ IR::Cast.new(
1234
+ target_type: type,
1235
+ expression: lower_inline_foreign_mapping_expression(
1236
+ argument.value,
1237
+ mapping_env:,
1238
+ replacements:,
1239
+ owner_analysis:,
1240
+ ),
1241
+ type:,
1242
+ )
1243
+ else
1244
+ lower_addr_expression(
1245
+ argument.value,
1246
+ env: mapping_env,
1247
+ target_type: type,
1248
+ )
1249
+ end
1250
+ else
1251
+ raise LoweringError, "unsupported inline foreign mapping call kind #{kind}"
1252
+ end
1253
+ end
1254
+
1255
+ def lower_inline_hash_operation_argument(expression, mapping_env:, replacements:, owner_analysis:, target_type:)
1256
+ actual_type = with_analysis_context(owner_analysis) do
1257
+ infer_expression_type(expression, env: mapping_env)
1258
+ end
1259
+ lowered_expression = lower_inline_foreign_mapping_expression(
1260
+ expression,
1261
+ mapping_env:,
1262
+ replacements:,
1263
+ owner_analysis:,
1264
+ )
1265
+ pointer_type = const_pointer_to(target_type)
1266
+
1267
+ if pointer_type?(actual_type) || ref_type?(actual_type)
1268
+ return cast_expression(lowered_expression, pointer_type)
1269
+ end
1270
+
1271
+ return cast_expression(lowered_expression.operand, pointer_type) if lowered_expression.is_a?(IR::Unary) && lowered_expression.operator == "*"
1272
+
1273
+ IR::AddressOf.new(expression: lowered_expression, type: pointer_type)
1274
+ end
1275
+
1276
+ def foreign_mapping_uses_inline_replacement?(expression, replacements)
1277
+ case expression
1278
+ when AST::Identifier
1279
+ replacements.key?(expression.name)
1280
+ when AST::MemberAccess
1281
+ foreign_mapping_uses_inline_replacement?(expression.receiver, replacements)
1282
+ when AST::IndexAccess
1283
+ foreign_mapping_uses_inline_replacement?(expression.receiver, replacements) ||
1284
+ foreign_mapping_uses_inline_replacement?(expression.index, replacements)
1285
+ when AST::Specialization, AST::Call
1286
+ foreign_mapping_uses_inline_replacement?(expression.callee, replacements) ||
1287
+ expression.arguments.any? { |argument| foreign_mapping_uses_inline_replacement?(argument.value, replacements) }
1288
+ when AST::UnaryOp
1289
+ foreign_mapping_uses_inline_replacement?(expression.operand, replacements)
1290
+ when AST::BinaryOp
1291
+ foreign_mapping_uses_inline_replacement?(expression.left, replacements) ||
1292
+ foreign_mapping_uses_inline_replacement?(expression.right, replacements)
1293
+ when AST::IfExpr
1294
+ foreign_mapping_uses_inline_replacement?(expression.condition, replacements) ||
1295
+ foreign_mapping_uses_inline_replacement?(expression.then_expression, replacements) ||
1296
+ foreign_mapping_uses_inline_replacement?(expression.else_expression, replacements)
1297
+ when AST::UnsafeExpr
1298
+ foreign_mapping_uses_inline_replacement?(expression.expression, replacements)
1299
+ when AST::PrefixCast
1300
+ foreign_mapping_uses_inline_replacement?(expression.expression, replacements)
1301
+ else
1302
+ false
1303
+ end
1304
+ end
1305
+
1306
+ def foreign_function_binding?(binding)
1307
+ binding.ast.is_a?(AST::ForeignFunctionDecl)
1308
+ end
1309
+
1310
+ def foreign_mapping_expression(decl)
1311
+ return decl.mapping unless foreign_mapping_auto_call_shorthand?(decl.mapping)
1312
+
1313
+ AST::Call.new(
1314
+ callee: decl.mapping,
1315
+ arguments: decl.params.map { |param| AST::Argument.new(name: nil, value: AST::Identifier.new(name: param.name)) },
1316
+ )
1317
+ end
1318
+
1319
+ def foreign_mapping_auto_call_shorthand?(expression)
1320
+ case expression
1321
+ when AST::Identifier
1322
+ true
1323
+ when AST::MemberAccess
1324
+ foreign_mapping_auto_call_shorthand?(expression.receiver)
1325
+ when AST::Specialization
1326
+ foreign_mapping_auto_call_shorthand?(expression.callee)
1327
+ else
1328
+ false
1329
+ end
1330
+ end
1331
+
1332
+ def foreign_mapping_public_alias_name(name)
1333
+ "#{name}_public"
1334
+ end
1335
+
1336
+ def substitute_foreign_mapping_expression(expression, replacements)
1337
+ case expression
1338
+ when AST::Identifier
1339
+ replacements.fetch(expression.name, expression)
1340
+ when AST::MemberAccess
1341
+ AST::MemberAccess.new(receiver: substitute_foreign_mapping_expression(expression.receiver, replacements), member: expression.member)
1342
+ when AST::IndexAccess
1343
+ AST::IndexAccess.new(
1344
+ receiver: substitute_foreign_mapping_expression(expression.receiver, replacements),
1345
+ index: substitute_foreign_mapping_expression(expression.index, replacements),
1346
+ )
1347
+ when AST::Specialization
1348
+ AST::Specialization.new(
1349
+ callee: substitute_foreign_mapping_expression(expression.callee, replacements),
1350
+ arguments: expression.arguments.map do |argument|
1351
+ AST::Argument.new(name: argument.name, value: substitute_foreign_mapping_expression(argument.value, replacements))
1352
+ end,
1353
+ )
1354
+ when AST::Call
1355
+ AST::Call.new(
1356
+ callee: substitute_foreign_mapping_expression(expression.callee, replacements),
1357
+ arguments: expression.arguments.map do |argument|
1358
+ AST::Argument.new(name: argument.name, value: substitute_foreign_mapping_expression(argument.value, replacements))
1359
+ end,
1360
+ )
1361
+ when AST::UnaryOp
1362
+ AST::UnaryOp.new(operator: expression.operator, operand: substitute_foreign_mapping_expression(expression.operand, replacements))
1363
+ when AST::BinaryOp
1364
+ AST::BinaryOp.new(
1365
+ operator: expression.operator,
1366
+ left: substitute_foreign_mapping_expression(expression.left, replacements),
1367
+ right: substitute_foreign_mapping_expression(expression.right, replacements),
1368
+ )
1369
+ when AST::IfExpr
1370
+ AST::IfExpr.new(
1371
+ condition: substitute_foreign_mapping_expression(expression.condition, replacements),
1372
+ then_expression: substitute_foreign_mapping_expression(expression.then_expression, replacements),
1373
+ else_expression: substitute_foreign_mapping_expression(expression.else_expression, replacements),
1374
+ )
1375
+ when AST::UnsafeExpr
1376
+ AST::UnsafeExpr.new(expression: substitute_foreign_mapping_expression(expression.expression, replacements))
1377
+ when AST::PrefixCast
1378
+ AST::PrefixCast.new(target_type: expression.target_type, expression: substitute_foreign_mapping_expression(expression.expression, replacements))
1379
+ else
1380
+ expression
1381
+ end
1382
+ end
1383
+
1384
+ def foreign_mapping_reference_counts(expression, counts = Hash.new(0))
1385
+ case expression
1386
+ when AST::Identifier
1387
+ counts[expression.name] += 1
1388
+ when AST::MemberAccess
1389
+ foreign_mapping_reference_counts(expression.receiver, counts)
1390
+ when AST::IndexAccess
1391
+ foreign_mapping_reference_counts(expression.receiver, counts)
1392
+ foreign_mapping_reference_counts(expression.index, counts)
1393
+ when AST::Specialization, AST::Call
1394
+ foreign_mapping_reference_counts(expression.callee, counts)
1395
+ expression.arguments.each { |argument| foreign_mapping_reference_counts(argument.value, counts) }
1396
+ when AST::UnaryOp
1397
+ foreign_mapping_reference_counts(expression.operand, counts)
1398
+ when AST::BinaryOp
1399
+ foreign_mapping_reference_counts(expression.left, counts)
1400
+ foreign_mapping_reference_counts(expression.right, counts)
1401
+ when AST::IfExpr
1402
+ foreign_mapping_reference_counts(expression.condition, counts)
1403
+ foreign_mapping_reference_counts(expression.then_expression, counts)
1404
+ foreign_mapping_reference_counts(expression.else_expression, counts)
1405
+ when AST::UnsafeExpr
1406
+ foreign_mapping_reference_counts(expression.expression, counts)
1407
+ when AST::PrefixCast
1408
+ foreign_mapping_reference_counts(expression.expression, counts)
1409
+ end
1410
+
1411
+ counts
1412
+ end
1413
+
1414
+ def duplicable_foreign_argument_expression?(expression)
1415
+ case expression
1416
+ when AST::Identifier, AST::IntegerLiteral, AST::FloatLiteral, AST::StringLiteral, AST::BooleanLiteral, AST::NullLiteral,
1417
+ IR::Name, IR::IntegerLiteral, IR::FloatLiteral, IR::StringLiteral, IR::BooleanLiteral, IR::NullLiteral
1418
+ true
1419
+ when AST::MemberAccess
1420
+ duplicable_foreign_argument_expression?(expression.receiver)
1421
+ when IR::Member
1422
+ duplicable_foreign_argument_expression?(expression.receiver)
1423
+ when AST::UnaryOp
1424
+ duplicable_foreign_argument_expression?(expression.operand)
1425
+ when IR::Unary
1426
+ duplicable_foreign_argument_expression?(expression.operand)
1427
+ when AST::BinaryOp
1428
+ duplicable_foreign_argument_expression?(expression.left) && duplicable_foreign_argument_expression?(expression.right)
1429
+ when IR::Binary
1430
+ duplicable_foreign_argument_expression?(expression.left) && duplicable_foreign_argument_expression?(expression.right)
1431
+ else
1432
+ false
1433
+ end
1434
+ end
1435
+
1436
+ def foreign_argument_needs_temporary_binding?(expression, reference_count:)
1437
+ return true if reference_count > 1 && !duplicable_foreign_argument_expression?(expression)
1438
+
1439
+ !inlineable_foreign_argument_expression?(expression)
1440
+ end
1441
+
1442
+ def automatic_foreign_cstr_list_temp_needed?(parameter, _expression, env: nil)
1443
+ return false unless parameter.type.is_a?(Types::Span) && parameter.type.element_type == @ctx.types.fetch("str")
1444
+ return false unless parameter.boundary_type.is_a?(Types::Span)
1445
+
1446
+ boundary_element_type = parameter.boundary_type.element_type
1447
+ boundary_element_type == @ctx.types.fetch("cstr") || char_pointer_type?(boundary_element_type)
1448
+ end
1449
+
1450
+ def automatic_foreign_cstr_temp_needed?(parameter, expression, env:)
1451
+ return false unless parameter.boundary_type == @ctx.types.fetch("cstr") && parameter.type == @ctx.types.fetch("str")
1452
+ return false if expression.is_a?(AST::StringLiteral) && !expression.cstring
1453
+ return false if cstr_backed_expression?(expression, env)
1454
+
1455
+ infer_expression_type(expression, env:) != @ctx.types.fetch("cstr")
1456
+ end
1457
+
1458
+ def automatic_variadic_foreign_cstr_temp_needed?(expression, env:)
1459
+ return false if expression.is_a?(AST::StringLiteral) && !expression.cstring
1460
+ return false if cstr_backed_expression?(expression, env)
1461
+
1462
+ infer_expression_type(expression, env:) == @ctx.types.fetch("str")
1463
+ end
1464
+
1465
+ def temporary_foreign_cstr_expression?(expression)
1466
+ expression.is_a?(IR::Call) && expression.callee == "mt_foreign_str_to_cstr_temp"
1467
+ end
1468
+
1469
+ def lower_specialization(expression, env:, type:)
1470
+ if expression.callee.is_a?(AST::Identifier) && expression.callee.name == "zero"
1471
+ return IR::ZeroInit.new(type:)
1472
+ end
1473
+
1474
+ if expression.callee.is_a?(AST::Identifier) && expression.callee.name == "default"
1475
+ resolution = resolve_default_specialization(expression, env:)
1476
+ return IR::Call.new(callee: resolution.callee_name, arguments: [], type:) if resolution.binding
1477
+
1478
+ return IR::ZeroInit.new(type:)
1479
+ end
1480
+
1481
+ if (literal = lower_compile_time_literal(compile_time_const_value(expression, env:), type))
1482
+ return literal
1483
+ end
1484
+
1485
+ if (callable_resolution = resolve_specialized_callable_binding(expression, env:))
1486
+ callable_kind, function_binding, = callable_resolution
1487
+ raise LoweringError, "specialized method must be called" if callable_kind == :method
1488
+
1489
+ raise LoweringError, "foreign function #{function_binding.name} cannot be used as a value" if foreign_function_binding?(function_binding)
1490
+
1491
+ if function_binding.external
1492
+ return IR::Name.new(name: external_function_c_name(function_binding), type:, pointer: false)
1493
+ end
1494
+
1495
+ return IR::Name.new(
1496
+ name: function_binding_c_name(function_binding, module_name: function_binding.owner.module_name),
1497
+ type:,
1498
+ pointer: false,
1499
+ )
1500
+ end
1501
+
1502
+ raise LoweringError, "specialization #{expression.callee.name} must be called" if expression.callee.is_a?(AST::Identifier)
1503
+
1504
+ raise LoweringError, "unsupported specialization #{expression.class.name}"
1505
+ end
1506
+
1507
+ def atomic_method_kind(receiver_type, name)
1508
+ return unless atomic_type?(receiver_type)
1509
+
1510
+ case name
1511
+ when "load" then :atomic_load
1512
+ when "store" then :atomic_store
1513
+ when "add" then :atomic_add
1514
+ when "sub" then :atomic_sub
1515
+ when "exchange" then :atomic_exchange
1516
+ when "compare_exchange" then :atomic_compare_exchange
1517
+ end
1518
+ end
1519
+
1520
+ def lower_atomic_method_call(kind, receiver, expression, env:, type:)
1521
+ receiver_type = infer_expression_type(receiver, env:)
1522
+ elem_type = atomic_element_type(receiver_type)
1523
+ ptr_type = Types::Registry.generic_instance("ptr", [elem_type])
1524
+ receiver_ir = lower_expression(receiver, env:)
1525
+ addr = IR::AddressOf.new(expression: receiver_ir, type: ptr_type)
1526
+ seq_cst = IR::IntegerLiteral.new(value: 5, type: @ctx.types.fetch("int"))
1527
+
1528
+ case kind
1529
+ when :atomic_load
1530
+ IR::Call.new(callee: "__atomic_load_n", arguments: [addr, seq_cst], type: elem_type)
1531
+ when :atomic_store
1532
+ arg = lower_contextual_expression(expression.arguments.first.value, env:, expected_type: elem_type)
1533
+ IR::Call.new(callee: "__atomic_store_n", arguments: [addr, arg, seq_cst], type:)
1534
+ when :atomic_add
1535
+ arg = lower_contextual_expression(expression.arguments.first.value, env:, expected_type: elem_type)
1536
+ IR::Call.new(callee: "__atomic_fetch_add", arguments: [addr, arg, seq_cst], type: elem_type)
1537
+ when :atomic_sub
1538
+ arg = lower_contextual_expression(expression.arguments.first.value, env:, expected_type: elem_type)
1539
+ IR::Call.new(callee: "__atomic_fetch_sub", arguments: [addr, arg, seq_cst], type: elem_type)
1540
+ when :atomic_exchange
1541
+ arg = lower_contextual_expression(expression.arguments.first.value, env:, expected_type: elem_type)
1542
+ IR::Call.new(callee: "__atomic_exchange_n", arguments: [addr, arg, seq_cst], type: elem_type)
1543
+ when :atomic_compare_exchange
1544
+ raise LoweringError, "atomic compare_exchange is not yet implemented in the built-in surface; use std.sync.AtomicUint for compare-exchange operations"
1545
+ end
1546
+ end
1547
+
1548
+ def lower_event_subscribe_call(expression, env:, runtime:, event_pointer:, type:, once: false)
1549
+ stateful = expression.arguments.length == 2
1550
+ callee_key = if stateful
1551
+ once ? :subscribe_once_stateful_linkage_name : :subscribe_stateful_linkage_name
1552
+ else
1553
+ once ? :subscribe_once_linkage_name : :subscribe_linkage_name
1554
+ end
1555
+ arguments = [event_pointer]
1556
+ if stateful
1557
+ arguments << lower_contextual_expression(expression.arguments.fetch(0).value, env:, expected_type: runtime.fetch(:void_ptr))
1558
+ arguments << lower_contextual_expression(expression.arguments.fetch(1).value, env:, expected_type: runtime.fetch(:void_ptr))
1559
+ else
1560
+ arguments << lower_contextual_expression(expression.arguments.fetch(0).value, env:, expected_type: runtime.fetch(:listener_type))
1561
+ end
1562
+ IR::Call.new(callee: runtime.fetch(callee_key), arguments:, type:)
1563
+ end
1564
+ end
1565
+ end