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,1645 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LowererExpressions
5
+ private
6
+
7
+
8
+ def prepare_expression_with_cleanups(expression, env:, expected_type: nil, allow_root_statement_foreign: false, materialize_array_calls: true, allow_void_propagation: false)
9
+ env[:prepared_expression_cleanups] ||= []
10
+ start_index = env[:prepared_expression_cleanups].length
11
+ setup, prepared_expression = prepare_expression_for_inline_lowering(
12
+ expression,
13
+ env:,
14
+ expected_type:,
15
+ allow_root_statement_foreign:,
16
+ materialize_array_calls:,
17
+ allow_void_propagation:,
18
+ )
19
+ cleanup_count = env[:prepared_expression_cleanups].length - start_index
20
+ cleanups = cleanup_count.positive? ? env[:prepared_expression_cleanups].slice!(start_index, cleanup_count) : []
21
+ [setup, prepared_expression, cleanups || []]
22
+ end
23
+
24
+ def prepare_expression_for_inline_lowering(expression, env:, expected_type: nil, allow_root_statement_foreign: false, materialize_array_calls: true, allow_void_propagation: false)
25
+ return [[], expression] unless expression
26
+
27
+ if expression.is_a?(AST::Call) &&
28
+ (foreign_call = foreign_call_info(expression, env)) && !allow_root_statement_foreign &&
29
+ foreign_call_requires_statement_lowering?(expression, foreign_call[:binding], env:)
30
+ type = infer_expression_type(expression, env:, expected_type:)
31
+ setup, value = lower_foreign_call_statement(foreign_call, env:, expected_type: type, statement_position: false)
32
+ return materialize_prepared_expression(setup, value, env:, type:, prefix: "foreign_expr")
33
+ end
34
+
35
+ case expression
36
+ when AST::FormatString
37
+ prepare_format_string_expression_for_inline_lowering(expression, env:)
38
+ when AST::MemberAccess
39
+ receiver_setup, receiver = prepare_expression_for_inline_lowering(expression.receiver, env:)
40
+ [receiver_setup, AST::MemberAccess.new(receiver:, member: expression.member)]
41
+ when AST::IndexAccess
42
+ receiver_setup, receiver = prepare_expression_for_inline_lowering(expression.receiver, env:)
43
+ index_setup, index = prepare_expression_for_inline_lowering(expression.index, env:)
44
+ [receiver_setup + index_setup, AST::IndexAccess.new(receiver:, index:)]
45
+ when AST::UnaryOp
46
+ return prepare_result_propagation_for_inline_lowering(expression, env:, allow_void_success: allow_void_propagation) if expression.operator == "?"
47
+
48
+ operand_setup, operand = prepare_expression_for_inline_lowering(expression.operand, env:, expected_type:)
49
+ [operand_setup, AST::UnaryOp.new(operator: expression.operator, operand:)]
50
+ when AST::BinaryOp
51
+ prepare_binary_expression_for_inline_lowering(expression, env:, expected_type:)
52
+ when AST::IfExpr
53
+ prepare_if_expression_for_inline_lowering(expression, env:, expected_type:)
54
+ when AST::MatchExpr
55
+ prepare_match_expression_for_inline_lowering(expression, env:, expected_type:)
56
+ when AST::UnsafeExpr
57
+ prepare_expression_for_inline_lowering(expression.expression, env:, expected_type:)
58
+ when AST::Call
59
+ prepare_call_expression_for_inline_lowering(
60
+ expression,
61
+ env:,
62
+ expected_type:,
63
+ allow_root_statement_foreign:,
64
+ materialize_array_calls:,
65
+ )
66
+ when AST::ProcExpr
67
+ proc_type = infer_expression_type(expression, env:, expected_type:)
68
+ setup, value = lower_proc_expression_for_local(expression, env:, local_name: fresh_c_temp_name(env, "proc_expr"), proc_type: proc_type)
69
+ materialize_prepared_expression(setup, value, env:, type: proc_type, prefix: "proc_expr")
70
+ when AST::PrefixCast
71
+ setup, prepared_expr = prepare_expression_for_inline_lowering(expression.expression, env:, expected_type:)
72
+ [setup, AST::PrefixCast.new(target_type: expression.target_type, expression: prepared_expr)]
73
+ else
74
+ [[], expression]
75
+ end
76
+ end
77
+
78
+ def prepare_call_expression_for_inline_lowering(expression, env:, expected_type: nil, allow_root_statement_foreign: false, materialize_array_calls: true)
79
+ kind, _callee_name, _receiver, callee_type, binding = resolve_callee(expression.callee, env, arguments: expression.arguments)
80
+
81
+ if binding && binding.respond_to?(:ast) && kind != :variant_arm_ctor && foreign_function_binding?(binding) && !allow_root_statement_foreign && foreign_call_requires_statement_lowering?(expression, binding, env:)
82
+ type = infer_expression_type(expression, env:, expected_type:)
83
+ setup, value = lower_foreign_call_statement({ call: expression, binding: binding }, env:, expected_type: type, statement_position: false)
84
+ return materialize_prepared_expression(setup, value, env:, type:, prefix: "foreign_expr")
85
+ end
86
+
87
+ callee_setup, callee = prepare_expression_for_inline_lowering(expression.callee, env:)
88
+ argument_setup = []
89
+ arguments = expression.arguments.map.with_index do |argument, index|
90
+ expected_arg_type = kind == :function || kind == :method || kind == :associated_method || kind == :callable_value ?
91
+ (index < callee_type.params.length ? callee_type.params[index].type : nil) : nil
92
+ argument_value = argument.value
93
+ argument_value = wrap_task_expression_in_root_proc(argument_value, env:) if task_expression_root_proc_bridge?(argument_value, expected_arg_type, env:)
94
+ argument_value = wrap_expression_in_ref_of(argument_value) if implicit_ref_argument_bridge?(argument_value, expected_arg_type, env:)
95
+ setup, prepared_value = prepare_expression_for_inline_lowering(argument_value, env:, expected_type: expected_arg_type)
96
+ argument_setup.concat(setup)
97
+ AST::Argument.new(name: argument.name, value: prepared_value)
98
+ end
99
+
100
+ prepared_call = AST::Call.new(callee:, arguments:)
101
+ return [callee_setup + argument_setup, prepared_call] unless materialize_array_calls && callee_type.respond_to?(:return_type) && array_type?(callee_type.return_type)
102
+
103
+ call_type = infer_expression_type(prepared_call, env:, expected_type:)
104
+ materialize_prepared_expression(
105
+ callee_setup + argument_setup,
106
+ lower_expression(prepared_call, env:, expected_type: call_type),
107
+ env:,
108
+ type: call_type,
109
+ prefix: "array_call",
110
+ )
111
+ end
112
+
113
+ def prepare_format_string_expression_for_inline_lowering(format_string, env:)
114
+ unless format_string_has_dynamic_parts?(format_string)
115
+ return [[], AST::StringLiteral.new(lexeme: "", value: format_string_static_text(format_string), cstring: false)]
116
+ end
117
+
118
+ setup, temp_name = build_dynamic_format_string_temp_setup(format_string, env:)
119
+ temp_value = IR::Name.new(name: temp_name, type: @ctx.types.fetch("str"), pointer: false)
120
+ (env[:prepared_expression_cleanups] ||= []) << [
121
+ IR::ExpressionStmt.new(
122
+ expression: IR::Call.new(
123
+ callee: "mt_format_str_release",
124
+ arguments: [temp_value],
125
+ type: @ctx.types.fetch("void"),
126
+ ),
127
+ ),
128
+ ]
129
+
130
+ [setup, AST::Identifier.new(name: temp_name)]
131
+ end
132
+
133
+ def build_dynamic_format_string_temp_setup(format_string, env:)
134
+ string_type = @ctx.types.fetch("str")
135
+ dest_name = env[:current_local_name] || env[:lowering_target_name]
136
+ if dest_name
137
+ env[:fmt_counter] ||= {}
138
+ env[:fmt_counter][dest_name] = (env[:fmt_counter][dest_name] || 0) + 1
139
+ suffix = env[:fmt_counter][dest_name] > 1 ? "_#{env[:fmt_counter][dest_name]}" : ""
140
+ base = "__fmt_#{dest_name}#{suffix}"
141
+ else
142
+ base = fresh_c_temp_name(env, "fmt_str")
143
+ end
144
+ temp_name = base
145
+ cap_name = "#{base}_cap"
146
+ off_name = "#{base}_off"
147
+ register_prepared_temp!(env, temp_name, string_type, cstr_backed: true)
148
+ total_cap_value = IR::Name.new(name: cap_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
149
+ result_value = IR::Name.new(name: temp_name, type: string_type, pointer: false)
150
+ offset_value = IR::Name.new(name: off_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
151
+
152
+ setup, format_parts = build_dynamic_format_string_parts(format_string, env:)
153
+ literal_capacity = format_parts.sum { |part| part[:kind] == :text ? part[:value].bytesize : 0 }
154
+
155
+ setup << IR::LocalDecl.new(
156
+ name: cap_name, linkage_name: cap_name, type: @ctx.types.fetch("ptr_uint"),
157
+ value: IR::IntegerLiteral.new(value: literal_capacity, type: @ctx.types.fetch("ptr_uint")),
158
+ )
159
+
160
+ format_parts.each do |part|
161
+ next if part[:kind] == :text
162
+ part_len = format_string_part_length_expression(part, env:)
163
+ setup << IR::Assignment.new(
164
+ target: total_cap_value, operator: "=",
165
+ value: IR::Binary.new(operator: "+", left: total_cap_value, right: part_len, type: @ctx.types.fetch("ptr_uint")),
166
+ )
167
+ end
168
+
169
+ setup << IR::LocalDecl.new(
170
+ name: temp_name, linkage_name: temp_name, type: string_type,
171
+ value: IR::Call.new(callee: "mt_format_str_make", arguments: [total_cap_value], type: string_type),
172
+ )
173
+ setup << IR::LocalDecl.new(
174
+ name: off_name, linkage_name: off_name, type: @ctx.types.fetch("ptr_uint"),
175
+ value: IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint")),
176
+ )
177
+
178
+ format_parts.each do |part|
179
+ setup.concat(format_string_part_append_statements(part, result_value, offset_value, env:))
180
+ end
181
+
182
+ [setup, temp_name]
183
+ end
184
+
185
+ def format_string_static_text(format_string)
186
+ format_string.parts.filter_map do |part|
187
+ next unless part.is_a?(AST::FormatTextPart)
188
+
189
+ part.value
190
+ end.join
191
+ end
192
+
193
+ def build_dynamic_format_string_parts(format_string, env:)
194
+ format_parts = []
195
+ setup = []
196
+
197
+ format_string.parts.each do |part|
198
+ if part.is_a?(AST::FormatTextPart)
199
+ next if part.value.empty?
200
+
201
+ format_parts << { kind: :text, value: part.value }
202
+ next
203
+ end
204
+
205
+ expression_setup, prepared_expression = prepare_expression_for_inline_lowering(part.expression, env:)
206
+ setup.concat(expression_setup)
207
+ value_type = infer_expression_type(prepared_expression, env:)
208
+
209
+ if part.format_spec
210
+ case part.format_spec[:kind]
211
+ when :precision
212
+ precision = part.format_spec[:value]
213
+ append_argument_type = @ctx.types.fetch("double")
214
+ parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
215
+ setup << IR::LocalDecl.new(
216
+ name: parameter_linkage_name,
217
+ linkage_name: parameter_linkage_name,
218
+ type: append_argument_type,
219
+ value: cast_expression(
220
+ lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
221
+ append_argument_type,
222
+ ),
223
+ )
224
+ format_parts << {
225
+ kind: :precision_expression,
226
+ append_function_name: "append_double_precision",
227
+ parameter_linkage_name: parameter_linkage_name,
228
+ parameter_type: append_argument_type,
229
+ precision: precision,
230
+ }
231
+ when :hex
232
+ append_function_name, append_argument_type = format_string_hex_append_plan(value_type, uppercase: part.format_spec[:uppercase])
233
+ parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
234
+ setup << IR::LocalDecl.new(
235
+ name: parameter_linkage_name,
236
+ linkage_name: parameter_linkage_name,
237
+ type: append_argument_type,
238
+ value: cast_expression(
239
+ lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
240
+ append_argument_type,
241
+ ),
242
+ )
243
+ format_parts << {
244
+ kind: :expression,
245
+ append_function_name: append_function_name,
246
+ parameter_linkage_name: parameter_linkage_name,
247
+ parameter_type: append_argument_type,
248
+ }
249
+ when :oct
250
+ append_function_name, append_argument_type = format_string_oct_append_plan(value_type, uppercase: part.format_spec[:uppercase])
251
+ parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
252
+ setup << IR::LocalDecl.new(
253
+ name: parameter_linkage_name,
254
+ linkage_name: parameter_linkage_name,
255
+ type: append_argument_type,
256
+ value: cast_expression(
257
+ lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
258
+ append_argument_type,
259
+ ),
260
+ )
261
+ format_parts << {
262
+ kind: :expression,
263
+ append_function_name: append_function_name,
264
+ parameter_linkage_name: parameter_linkage_name,
265
+ parameter_type: append_argument_type,
266
+ }
267
+ when :bin
268
+ append_function_name, append_argument_type = format_string_bin_append_plan(value_type, uppercase: part.format_spec[:uppercase])
269
+ parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
270
+ setup << IR::LocalDecl.new(
271
+ name: parameter_linkage_name,
272
+ linkage_name: parameter_linkage_name,
273
+ type: append_argument_type,
274
+ value: cast_expression(
275
+ lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
276
+ append_argument_type,
277
+ ),
278
+ )
279
+ format_parts << {
280
+ kind: :expression,
281
+ append_function_name: append_function_name,
282
+ parameter_linkage_name: parameter_linkage_name,
283
+ parameter_type: append_argument_type,
284
+ }
285
+ else
286
+ raise LoweringError, "unsupported format spec #{part.format_spec.inspect}"
287
+ end
288
+ else
289
+ append_plan = format_string_append_plan(value_type, context: "formatted string interpolation of #{value_type}")
290
+ parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
291
+ setup << IR::LocalDecl.new(
292
+ name: parameter_linkage_name,
293
+ linkage_name: parameter_linkage_name,
294
+ type: append_plan[:append_argument_type],
295
+ value: cast_expression(
296
+ lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
297
+ append_plan[:append_argument_type],
298
+ ),
299
+ )
300
+
301
+ if append_plan[:kind] == :custom
302
+ register_prepared_temp!(env, parameter_linkage_name, append_plan[:append_argument_type])
303
+ part_info = {
304
+ kind: :custom_expression,
305
+ parameter_linkage_name: parameter_linkage_name,
306
+ parameter_type: append_plan[:append_argument_type],
307
+ format_binding: append_plan[:binding],
308
+ append_output_type: append_plan[:append_output_type],
309
+ }
310
+ expected_length_linkage_name = fresh_c_temp_name(env, "fmt_part_len")
311
+ setup << IR::LocalDecl.new(
312
+ name: expected_length_linkage_name,
313
+ linkage_name: expected_length_linkage_name,
314
+ type: @ctx.types.fetch("ptr_uint"),
315
+ value: IR::Call.new(
316
+ callee: append_plan[:binding].length_callee_name,
317
+ arguments: [format_string_custom_receiver_argument(part_info, hook: :length, env:)],
318
+ type: @ctx.types.fetch("ptr_uint"),
319
+ ),
320
+ )
321
+ format_parts << part_info.merge(expected_length_linkage_name:)
322
+ else
323
+ format_parts << {
324
+ kind: :expression,
325
+ append_function_name: append_plan[:append_function_name],
326
+ parameter_linkage_name: parameter_linkage_name,
327
+ parameter_type: append_plan[:append_argument_type],
328
+ }
329
+ end
330
+ end
331
+ end
332
+
333
+ [setup, format_parts]
334
+ end
335
+
336
+ def format_string_has_dynamic_parts?(format_string)
337
+ format_string.parts.any? { |part| part.is_a?(AST::FormatExprPart) }
338
+ end
339
+
340
+ def explicit_format_sink_call_info(expression, env)
341
+ return unless expression.is_a?(AST::Call)
342
+
343
+ kind, _callee_name, receiver, callee_type, callee_binding = resolve_callee(expression.callee, env, arguments: expression.arguments)
344
+
345
+ case kind
346
+ when :function
347
+ return unless callee_binding&.owner&.module_name == "std.fmt"
348
+ return unless expression.arguments.length == 2
349
+
350
+ operation = case callee_binding.name
351
+ when "append_format"
352
+ :append
353
+ when "assign_format"
354
+ :assign
355
+ end
356
+ return unless operation
357
+
358
+ format_string = expression.arguments.fetch(1).value
359
+ return unless format_string.is_a?(AST::FormatString)
360
+
361
+ {
362
+ operation:,
363
+ sink_expression: expression.arguments.fetch(0).value,
364
+ sink_expected_type: callee_type.params.fetch(0).type,
365
+ format_string:,
366
+ sink_kind: :string,
367
+ method_call: false,
368
+ callee_type:,
369
+ callee_binding:,
370
+ }
371
+ when :method
372
+ return unless callee_binding&.owner&.module_name == "std.string"
373
+ return unless string_builder_type?(callee_type.receiver_type)
374
+ return unless expression.arguments.length == 1
375
+
376
+ operation = case callee_binding.name
377
+ when "append_format"
378
+ :append
379
+ when "assign_format"
380
+ :assign
381
+ end
382
+ return unless operation
383
+
384
+ format_string = expression.arguments.fetch(0).value
385
+ return unless format_string.is_a?(AST::FormatString)
386
+
387
+ {
388
+ operation:,
389
+ sink_expression: receiver,
390
+ sink_expected_type: callee_type.receiver_type,
391
+ format_string:,
392
+ sink_kind: :string,
393
+ method_call: true,
394
+ callee_type:,
395
+ callee_binding:,
396
+ }
397
+ when :str_buffer_append_format, :str_buffer_assign_format
398
+ return unless expression.arguments.length == 1
399
+
400
+ format_string = expression.arguments.fetch(0).value
401
+ return unless format_string.is_a?(AST::FormatString)
402
+
403
+ {
404
+ operation: kind == :str_buffer_assign_format ? :assign : :append,
405
+ sink_expression: receiver,
406
+ sink_expected_type: callee_type.receiver_type,
407
+ format_string:,
408
+ sink_kind: :str_buffer,
409
+ method_call: false,
410
+ callee_type:,
411
+ callee_binding: nil,
412
+ }
413
+ end
414
+ end
415
+
416
+ def explicit_format_sink_target(info, prepared_sink_expression, env:)
417
+ case info[:sink_kind]
418
+ when :string
419
+ sink_value = if info[:method_call]
420
+ lower_method_receiver_argument(prepared_sink_expression, info[:callee_type], info[:callee_binding], env:)
421
+ else
422
+ lower_contextual_expression(prepared_sink_expression, env:, expected_type: info[:sink_expected_type])
423
+ end
424
+
425
+ { kind: :string, value: sink_value }
426
+ when :str_buffer
427
+ lowered_receiver = lower_expression(prepared_sink_expression, env:)
428
+ {
429
+ kind: :str_buffer,
430
+ receiver: lowered_receiver,
431
+ data_pointer: lower_str_buffer_data_pointer_from_lowered(lowered_receiver),
432
+ len_pointer: lower_str_buffer_len_pointer_from_lowered(lowered_receiver),
433
+ dirty_pointer: lower_str_buffer_dirty_pointer_from_lowered(lowered_receiver),
434
+ capacity: IR::IntegerLiteral.new(value: str_buffer_capacity(lowered_receiver.type), type: @ctx.types.fetch("ptr_uint")),
435
+ }
436
+ else
437
+ raise LoweringError, "unsupported explicit format sink #{info[:sink_kind]}"
438
+ end
439
+ end
440
+
441
+ def explicit_format_sink_target_buffer_view(sink_target)
442
+ case sink_target[:kind]
443
+ when :string
444
+ sink_target[:value]
445
+ when :str_buffer
446
+ IR::AggregateLiteral.new(
447
+ type: @ctx.types.fetch("str"),
448
+ fields: [
449
+ IR::AggregateField.new(name: "data", value: sink_target[:data_pointer]),
450
+ IR::AggregateField.new(name: "len", value: sink_target[:capacity]),
451
+ ],
452
+ )
453
+ else
454
+ raise LoweringError, "unsupported explicit format sink #{sink_target[:kind]}"
455
+ end
456
+ end
457
+
458
+ def lower_explicit_format_sink_expression_statement(expression, env:, line:)
459
+ info = explicit_format_sink_call_info(expression, env)
460
+ return unless info
461
+
462
+ sink_setup, prepared_sink_expression, sink_cleanups = prepare_expression_with_cleanups(
463
+ info[:sink_expression],
464
+ env:,
465
+ expected_type: info[:sink_expected_type],
466
+ allow_root_statement_foreign: true,
467
+ )
468
+ sink_target = explicit_format_sink_target(info, prepared_sink_expression, env:)
469
+
470
+ unless format_string_has_dynamic_parts?(info[:format_string])
471
+ return sink_setup + [
472
+ IR::ExpressionStmt.new(
473
+ expression: explicit_format_sink_runtime_call(
474
+ operation: info[:operation],
475
+ sink_target:,
476
+ text_value: IR::StringLiteral.new(
477
+ value: format_string_static_text(info[:format_string]),
478
+ type: @ctx.types.fetch("str"),
479
+ cstring: false,
480
+ ),
481
+ ),
482
+ line:,
483
+ source_path: @ctx.current_analysis_path,
484
+ ),
485
+ *sink_cleanups.flat_map(&:itself),
486
+ ]
487
+ end
488
+
489
+ format_cleanup_start = (env[:prepared_expression_cleanups] ||= []).length
490
+ format_setup, format_parts = build_dynamic_format_string_parts(info[:format_string], env:)
491
+ format_cleanup_count = env[:prepared_expression_cleanups].length - format_cleanup_start
492
+ format_cleanups = format_cleanup_count.positive? ? env[:prepared_expression_cleanups].slice!(format_cleanup_start, format_cleanup_count) : []
493
+ copied_part_setup, copied_parts, copied_part_cleanups = copy_explicit_format_sink_str_parts(
494
+ format_parts,
495
+ env:,
496
+ sink_kind: info[:sink_kind],
497
+ )
498
+
499
+ sink_statements = sink_setup + format_setup + copied_part_setup
500
+ case sink_target[:kind]
501
+ when :string
502
+ if info[:operation] == :assign
503
+ sink_statements << IR::ExpressionStmt.new(
504
+ expression: IR::Call.new(callee: "std_string_String_clear", arguments: [sink_target[:value]], type: @ctx.types.fetch("void")),
505
+ line:,
506
+ source_path: @ctx.current_analysis_path,
507
+ )
508
+ end
509
+
510
+ copied_parts.each do |part|
511
+ sink_statements << IR::ExpressionStmt.new(
512
+ expression: explicit_format_sink_append_call(part, sink_value: sink_target[:value], env:),
513
+ line:,
514
+ source_path: @ctx.current_analysis_path,
515
+ )
516
+ end
517
+ when :str_buffer
518
+ if info[:operation] == :assign
519
+ sink_statements << IR::ExpressionStmt.new(
520
+ expression: IR::Call.new(
521
+ callee: "mt_str_buffer_clear",
522
+ arguments: [
523
+ sink_target[:data_pointer],
524
+ sink_target[:capacity],
525
+ sink_target[:len_pointer],
526
+ sink_target[:dirty_pointer],
527
+ ],
528
+ type: @ctx.types.fetch("void"),
529
+ ),
530
+ line:,
531
+ source_path: @ctx.current_analysis_path,
532
+ )
533
+ end
534
+
535
+ offset_name = fresh_c_temp_name(env, "fmt_sink_offset")
536
+ offset_value = IR::Name.new(name: offset_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
537
+ offset_init = if info[:operation] == :assign
538
+ IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint"))
539
+ else
540
+ IR::Call.new(
541
+ callee: "mt_str_buffer_len",
542
+ arguments: [
543
+ sink_target[:data_pointer],
544
+ sink_target[:capacity],
545
+ sink_target[:len_pointer],
546
+ sink_target[:dirty_pointer],
547
+ ],
548
+ type: @ctx.types.fetch("ptr_uint"),
549
+ )
550
+ end
551
+ sink_statements << IR::LocalDecl.new(name: offset_name, linkage_name: offset_name, type: @ctx.types.fetch("ptr_uint"), value: offset_init)
552
+
553
+ target_value = explicit_format_sink_target_buffer_view(sink_target)
554
+ copied_parts.each do |part|
555
+ sink_statements.concat(format_string_part_append_statements(part, target_value, offset_value, env:))
556
+ end
557
+ sink_statements << IR::Assignment.new(
558
+ target: IR::Unary.new(operator: "*", operand: sink_target[:len_pointer], type: @ctx.types.fetch("ptr_uint")),
559
+ operator: "=",
560
+ value: offset_value,
561
+ )
562
+ else
563
+ raise LoweringError, "unsupported explicit format sink #{sink_target[:kind]}"
564
+ end
565
+
566
+ sink_statements.concat(copied_part_cleanups)
567
+ sink_statements.concat(sink_cleanups.flat_map(&:itself))
568
+ sink_statements.concat(format_cleanups.flat_map(&:itself))
569
+ sink_statements
570
+ end
571
+
572
+ def explicit_format_sink_runtime_call(operation:, sink_target:, text_value:)
573
+ case sink_target[:kind]
574
+ when :string
575
+ callee = operation == :assign ? "std_string_String_assign" : "std_string_String_append"
576
+ IR::Call.new(callee:, arguments: [sink_target[:value], text_value], type: @ctx.types.fetch("void"))
577
+ when :str_buffer
578
+ callee = operation == :assign ? "mt_str_buffer_assign" : "mt_str_buffer_append"
579
+ IR::Call.new(
580
+ callee:,
581
+ arguments: [
582
+ text_value,
583
+ sink_target[:data_pointer],
584
+ sink_target[:capacity],
585
+ sink_target[:len_pointer],
586
+ sink_target[:dirty_pointer],
587
+ ],
588
+ type: @ctx.types.fetch("void"),
589
+ )
590
+ else
591
+ raise LoweringError, "unsupported explicit format sink #{sink_target[:kind]}"
592
+ end
593
+ end
594
+
595
+ def copy_explicit_format_sink_str_parts(format_parts, env:, sink_kind:)
596
+ setup = []
597
+ cleanup = []
598
+
599
+ copied_parts = format_parts.map do |part|
600
+ next part unless part[:kind] == :expression
601
+
602
+ should_copy = part[:append_function_name] == "append" ||
603
+ (sink_kind == :str_buffer && part[:append_function_name] == "append_cstr")
604
+ next part unless should_copy
605
+
606
+ parameter = format_string_part_parameter_expression(part)
607
+ copy_name = fresh_c_temp_name(env, "fmt_sink_str")
608
+ copy_value = IR::Name.new(name: copy_name, type: @ctx.types.fetch("str"), pointer: false)
609
+ register_prepared_temp!(env, copy_name, @ctx.types.fetch("str"), cstr_backed: true)
610
+
611
+ length_value = if part[:append_function_name] == "append"
612
+ IR::Member.new(receiver: parameter, member: "len", type: @ctx.types.fetch("ptr_uint"))
613
+ else
614
+ IR::Call.new(callee: "mt_format_cstr_len", arguments: [parameter], type: @ctx.types.fetch("ptr_uint"))
615
+ end
616
+ append_callee = part[:append_function_name] == "append" ? "mt_format_append_str" : "mt_format_append_cstr"
617
+
618
+ setup << IR::LocalDecl.new(
619
+ name: copy_name,
620
+ linkage_name: copy_name,
621
+ type: @ctx.types.fetch("str"),
622
+ value: IR::Call.new(
623
+ callee: "mt_format_str_make",
624
+ arguments: [length_value],
625
+ type: @ctx.types.fetch("str"),
626
+ ),
627
+ )
628
+ setup << IR::ExpressionStmt.new(
629
+ expression: IR::Call.new(
630
+ callee: append_callee,
631
+ arguments: [copy_value, IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint")), parameter],
632
+ type: @ctx.types.fetch("ptr_uint"),
633
+ ),
634
+ )
635
+ cleanup << IR::ExpressionStmt.new(
636
+ expression: IR::Call.new(callee: "mt_format_str_release", arguments: [copy_value], type: @ctx.types.fetch("void")),
637
+ )
638
+
639
+ part.merge(parameter_linkage_name: copy_name, parameter_type: @ctx.types.fetch("str"), append_function_name: "append")
640
+ end
641
+
642
+ [setup, copied_parts, cleanup]
643
+ end
644
+
645
+ def explicit_format_sink_append_call(part, sink_value:, env:)
646
+ if part[:kind] == :text
647
+ return IR::Call.new(
648
+ callee: "std_string_String_append",
649
+ arguments: [sink_value, IR::StringLiteral.new(value: part[:value], type: @ctx.types.fetch("str"), cstring: false)],
650
+ type: @ctx.types.fetch("void"),
651
+ )
652
+ end
653
+
654
+ parameter = format_string_part_parameter_expression(part)
655
+
656
+ if part[:kind] == :precision_expression
657
+ return IR::Call.new(
658
+ callee: "std_fmt_append_double_precision",
659
+ arguments: [sink_value, parameter, IR::IntegerLiteral.new(value: part[:precision], type: @ctx.types.fetch("int"))],
660
+ type: @ctx.types.fetch("void"),
661
+ )
662
+ end
663
+
664
+ if part[:kind] == :custom_expression
665
+ return IR::Call.new(
666
+ callee: part[:format_binding].append_callee_name,
667
+ arguments: [
668
+ format_string_custom_receiver_argument(part, hook: :append, env:),
669
+ sink_value,
670
+ ],
671
+ type: @ctx.types.fetch("void"),
672
+ )
673
+ end
674
+
675
+ callee = case part[:append_function_name]
676
+ when "append"
677
+ "std_string_String_append"
678
+ when "append_cstr"
679
+ "std_fmt_append_cstr"
680
+ else
681
+ "std_fmt_#{part[:append_function_name]}"
682
+ end
683
+
684
+ IR::Call.new(callee:, arguments: [sink_value, parameter], type: @ctx.types.fetch("void"))
685
+ end
686
+
687
+ def format_string_part_length_expression(part, env:)
688
+ parameter = format_string_part_parameter_expression(part)
689
+
690
+ if part[:kind] == :precision_expression
691
+ return IR::Call.new(
692
+ callee: "mt_format_double_precision_len",
693
+ arguments: [parameter, IR::IntegerLiteral.new(value: part[:precision], type: @ctx.types.fetch("int"))],
694
+ type: @ctx.types.fetch("ptr_uint"),
695
+ )
696
+ end
697
+
698
+ if part[:kind] == :custom_expression
699
+ return IR::Name.new(name: part[:expected_length_linkage_name], type: @ctx.types.fetch("ptr_uint"), pointer: false)
700
+ end
701
+
702
+ case part[:append_function_name]
703
+ when "append"
704
+ IR::Member.new(receiver: parameter, member: "len", type: @ctx.types.fetch("ptr_uint"))
705
+ when "append_cstr"
706
+ IR::Call.new(callee: "mt_format_cstr_len", arguments: [parameter], type: @ctx.types.fetch("ptr_uint"))
707
+ else
708
+ IR::Call.new(callee: mt_format_length_c_name(part[:append_function_name]), arguments: [parameter], type: @ctx.types.fetch("ptr_uint"))
709
+ end
710
+ end
711
+
712
+ def format_string_part_append_statements(part, result_value, offset_value, env:)
713
+ if part[:kind] == :custom_expression
714
+ output_type = part[:append_output_type]
715
+ output_ref_type = Types::Registry.generic_instance("ref", [output_type])
716
+ output_value_name = fresh_c_temp_name(env, "fmt_part_output")
717
+ output_value = IR::Name.new(name: output_value_name, type: output_type, pointer: false)
718
+ output_len = IR::Member.new(receiver: output_value, member: "len", type: @ctx.types.fetch("ptr_uint"))
719
+ expected_length = IR::Name.new(name: part[:expected_length_linkage_name], type: @ctx.types.fetch("ptr_uint"), pointer: false)
720
+ data_pointer = format_string_result_data_pointer(result_value)
721
+ slice_data_pointer = cast_expression(
722
+ IR::Binary.new(operator: "+", left: data_pointer, right: offset_value, type: pointer_to(@ctx.types.fetch("char"))),
723
+ output_type.field("data"),
724
+ )
725
+
726
+ return [
727
+ IR::LocalDecl.new(
728
+ name: output_value_name,
729
+ linkage_name: output_value_name,
730
+ type: output_type,
731
+ value: IR::AggregateLiteral.new(
732
+ type: output_type,
733
+ fields: [
734
+ IR::AggregateField.new(name: "data", value: slice_data_pointer),
735
+ IR::AggregateField.new(
736
+ name: "len",
737
+ value: IR::IntegerLiteral.new(value: 0, type: output_type.field("len")),
738
+ ),
739
+ IR::AggregateField.new(name: "capacity", value: expected_length),
740
+ IR::AggregateField.new(
741
+ name: "owns_storage",
742
+ value: IR::BooleanLiteral.new(value: false, type: output_type.field("owns_storage")),
743
+ ),
744
+ ],
745
+ ),
746
+ ),
747
+ IR::ExpressionStmt.new(
748
+ expression: IR::Call.new(
749
+ callee: part[:format_binding].append_callee_name,
750
+ arguments: [
751
+ format_string_custom_receiver_argument(part, hook: :append, env:),
752
+ IR::AddressOf.new(expression: output_value, type: output_ref_type),
753
+ ],
754
+ type: @ctx.types.fetch("void"),
755
+ ),
756
+ ),
757
+ IR::IfStmt.new(
758
+ condition: IR::Binary.new(operator: "!=", left: output_len, right: expected_length, type: @ctx.types.fetch("bool")),
759
+ then_body: [
760
+ IR::ExpressionStmt.new(
761
+ expression: IR::Call.new(
762
+ callee: "mt_fatal",
763
+ arguments: [
764
+ IR::StringLiteral.new(
765
+ value: "custom format hook length mismatch",
766
+ type: @ctx.types.fetch("cstr"),
767
+ cstring: true,
768
+ ),
769
+ ],
770
+ type: @ctx.types.fetch("void"),
771
+ ),
772
+ ),
773
+ ],
774
+ else_body: nil,
775
+ ),
776
+ IR::Assignment.new(
777
+ target: offset_value,
778
+ operator: "=",
779
+ value: IR::Binary.new(
780
+ operator: "+",
781
+ left: offset_value,
782
+ right: output_len,
783
+ type: @ctx.types.fetch("ptr_uint"),
784
+ ),
785
+ ),
786
+ IR::Assignment.new(
787
+ target: IR::Index.new(receiver: data_pointer, index: offset_value, type: @ctx.types.fetch("char")),
788
+ operator: "=",
789
+ value: IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("char")),
790
+ ),
791
+ ]
792
+ end
793
+
794
+ [
795
+ IR::Assignment.new(
796
+ target: offset_value,
797
+ operator: "=",
798
+ value: format_string_part_append_expression(part, result_value, offset_value),
799
+ ),
800
+ ]
801
+ end
802
+
803
+ def format_string_part_append_expression(part, result_value, offset_value)
804
+ if part[:kind] == :text
805
+ return IR::Call.new(
806
+ callee: "mt_format_append_str",
807
+ arguments: [result_value, offset_value, IR::StringLiteral.new(value: part[:value], type: @ctx.types.fetch("str"), cstring: false)],
808
+ type: @ctx.types.fetch("ptr_uint"),
809
+ )
810
+ end
811
+
812
+ parameter = format_string_part_parameter_expression(part)
813
+
814
+ if part[:kind] == :precision_expression
815
+ return IR::Call.new(
816
+ callee: "mt_format_append_double_precision",
817
+ arguments: [result_value, offset_value, parameter, IR::IntegerLiteral.new(value: part[:precision], type: @ctx.types.fetch("int"))],
818
+ type: @ctx.types.fetch("ptr_uint"),
819
+ )
820
+ end
821
+
822
+ if part[:kind] == :custom_expression
823
+ raise LoweringError, "custom format parts require statement lowering"
824
+ end
825
+
826
+ IR::Call.new(
827
+ callee: mt_format_append_c_name(part[:append_function_name]),
828
+ arguments: [result_value, offset_value, parameter],
829
+ type: @ctx.types.fetch("ptr_uint"),
830
+ )
831
+ end
832
+
833
+ def format_string_custom_receiver_argument(part, hook:, env:)
834
+ binding = case hook
835
+ when :length
836
+ part[:format_binding].length_binding
837
+ when :append
838
+ part[:format_binding].append_binding
839
+ else
840
+ raise LoweringError, "unsupported custom format hook #{hook}"
841
+ end
842
+
843
+ if env
844
+ return lower_method_receiver_argument(AST::Identifier.new(name: part[:parameter_linkage_name]), binding.type, binding, env:)
845
+ end
846
+
847
+ IR::Name.new(name: part[:parameter_linkage_name], type: part[:parameter_type], pointer: false)
848
+ end
849
+
850
+ def format_string_result_data_pointer(result_value)
851
+ IR::Member.new(receiver: result_value, member: "data", type: pointer_to(@ctx.types.fetch("char")))
852
+ end
853
+
854
+ def format_string_part_parameter_expression(part)
855
+ IR::Name.new(name: part[:parameter_linkage_name], type: part[:parameter_type], pointer: false)
856
+ end
857
+
858
+ def format_string_append_plan(type, context:)
859
+ return { kind: :builtin, append_function_name: "append", append_argument_type: @ctx.types.fetch("str") } if type == @ctx.types.fetch("str")
860
+ return { kind: :builtin, append_function_name: "append_cstr", append_argument_type: @ctx.types.fetch("cstr") } if type == @ctx.types.fetch("cstr")
861
+ return { kind: :builtin, append_function_name: "append_bool", append_argument_type: @ctx.types.fetch("bool") } if type == @ctx.types.fetch("bool")
862
+ return { kind: :builtin, append_function_name: "append_float", append_argument_type: @ctx.types.fetch("float") } if type == @ctx.types.fetch("float")
863
+ return { kind: :builtin, append_function_name: "append_double", append_argument_type: @ctx.types.fetch("double") } if type == @ctx.types.fetch("double")
864
+
865
+ if type.is_a?(Types::Primitive) && type.integer?
866
+ return { kind: :builtin, append_function_name: "append_int", append_argument_type: @ctx.types.fetch("int") } if %w[byte short int].include?(type.name)
867
+ return { kind: :builtin, append_function_name: "append_uint", append_argument_type: @ctx.types.fetch("uint") } if %w[ubyte ushort uint].include?(type.name)
868
+ return { kind: :builtin, append_function_name: "append_ptr_uint", append_argument_type: @ctx.types.fetch("ptr_uint") } if type.name == "ptr_uint"
869
+ return { kind: :builtin, append_function_name: "append_long", append_argument_type: @ctx.types.fetch("long") } if %w[long ptr_int].include?(type.name)
870
+ return { kind: :builtin, append_function_name: "append_ulong", append_argument_type: @ctx.types.fetch("ulong") } if type.name == "ulong"
871
+ end
872
+
873
+ if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
874
+ return format_string_append_plan(type.backing_type, context:)
875
+ end
876
+
877
+ if (custom_binding = resolve_explicit_format_binding(type, context:))
878
+ return {
879
+ kind: :custom,
880
+ append_argument_type: type,
881
+ binding: custom_binding,
882
+ append_output_type: referenced_type(custom_binding.append_binding.type.params.first.type),
883
+ }
884
+ end
885
+
886
+ raise LoweringError, "formatted string interpolation supports str, cstr, bool, numeric primitives, integer-backed enums/flags, and types implementing format_len()/append_format(output: ref[std.string.String]), got #{type}"
887
+ end
888
+
889
+ def format_string_hex_append_plan(type, uppercase:)
890
+ if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
891
+ return format_string_hex_append_plan(type.backing_type, uppercase:)
892
+ end
893
+
894
+ unless type.is_a?(Types::Primitive) && type.integer?
895
+ raise LoweringError, "format spec ':x' and ':X' require integer interpolation, got #{type}"
896
+ end
897
+
898
+ if type.signed_integer?
899
+ return [uppercase ? "append_long_hex_upper" : "append_long_hex", @ctx.types.fetch("long")]
900
+ end
901
+
902
+ if type.unsigned_integer?
903
+ return [uppercase ? "append_ulong_hex_upper" : "append_ulong_hex", @ctx.types.fetch("ulong")]
904
+ end
905
+
906
+ raise LoweringError, "format spec ':x' and ':X' require integer interpolation, got #{type}"
907
+ end
908
+
909
+ def format_string_oct_append_plan(type, uppercase:)
910
+ _ = uppercase
911
+ if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
912
+ return format_string_oct_append_plan(type.backing_type, uppercase:)
913
+ end
914
+
915
+ unless type.is_a?(Types::Primitive) && type.integer?
916
+ raise LoweringError, "format spec ':o' and ':O' require integer interpolation, got #{type}"
917
+ end
918
+
919
+ if type.signed_integer?
920
+ return ["append_long_oct", @ctx.types.fetch("long")]
921
+ end
922
+
923
+ if type.unsigned_integer?
924
+ return ["append_ulong_oct", @ctx.types.fetch("ulong")]
925
+ end
926
+
927
+ raise LoweringError, "format spec ':o' and ':O' require integer interpolation, got #{type}"
928
+ end
929
+
930
+ def format_string_bin_append_plan(type, uppercase:)
931
+ _ = uppercase
932
+ if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
933
+ return format_string_bin_append_plan(type.backing_type, uppercase:)
934
+ end
935
+
936
+ unless type.is_a?(Types::Primitive) && type.integer?
937
+ raise LoweringError, "format spec ':b' and ':B' require integer interpolation, got #{type}"
938
+ end
939
+
940
+ if type.signed_integer?
941
+ return ["append_long_bin", @ctx.types.fetch("long")]
942
+ end
943
+
944
+ if type.unsigned_integer?
945
+ return ["append_ulong_bin", @ctx.types.fetch("ulong")]
946
+ end
947
+
948
+ raise LoweringError, "format spec ':b' and ':B' require integer interpolation, got #{type}"
949
+ end
950
+
951
+ def mt_format_length_c_name(name)
952
+ {
953
+ "append_bool" => "mt_format_bool_len",
954
+ "append_float" => "mt_format_float_len",
955
+ "append_double" => "mt_format_double_len",
956
+ "append_ulong_hex" => "mt_format_ulong_hex_len",
957
+ "append_ulong_hex_upper" => "mt_format_ulong_hex_len",
958
+ "append_long_hex" => "mt_format_long_hex_len",
959
+ "append_long_hex_upper" => "mt_format_long_hex_len",
960
+ "append_ulong_oct" => "mt_format_ulong_oct_len",
961
+ "append_long_oct" => "mt_format_long_oct_len",
962
+ "append_ulong_bin" => "mt_format_ulong_bin_len",
963
+ "append_long_bin" => "mt_format_long_bin_len",
964
+ "append_int" => "mt_format_int_len",
965
+ "append_uint" => "mt_format_uint_len",
966
+ "append_ptr_uint" => "mt_format_ptr_uint_len",
967
+ "append_long" => "mt_format_long_len",
968
+ "append_ulong" => "mt_format_ulong_len",
969
+ }.fetch(name)
970
+ end
971
+
972
+ def mt_format_append_c_name(name)
973
+ {
974
+ "append" => "mt_format_append_str",
975
+ "append_cstr" => "mt_format_append_cstr",
976
+ "append_bool" => "mt_format_append_bool",
977
+ "append_float" => "mt_format_append_float",
978
+ "append_double" => "mt_format_append_double",
979
+ "append_ulong_hex" => "mt_format_append_ulong_hex",
980
+ "append_ulong_hex_upper" => "mt_format_append_ulong_hex_upper",
981
+ "append_long_hex" => "mt_format_append_long_hex",
982
+ "append_long_hex_upper" => "mt_format_append_long_hex_upper",
983
+ "append_ulong_oct" => "mt_format_append_ulong_oct",
984
+ "append_long_oct" => "mt_format_append_long_oct",
985
+ "append_ulong_bin" => "mt_format_append_ulong_bin",
986
+ "append_long_bin" => "mt_format_append_long_bin",
987
+ "append_int" => "mt_format_append_int",
988
+ "append_uint" => "mt_format_append_uint",
989
+ "append_ptr_uint" => "mt_format_append_ptr_uint",
990
+ "append_long" => "mt_format_append_long",
991
+ "append_ulong" => "mt_format_append_ulong",
992
+ }.fetch(name)
993
+ end
994
+
995
+ def prepare_binary_expression_for_inline_lowering(expression, env:, expected_type: nil)
996
+ propagated_type = propagating_expected_type(expression.operator, expected_type)
997
+ left_type, right_type = infer_binary_operand_types(expression, env:, expected_type:)
998
+ operand_type = promoted_binary_operand_type(expression.operator, left_type, right_type)
999
+ left_setup, left = prepare_expression_for_inline_lowering(expression.left, env:, expected_type: operand_type || propagated_type || left_type)
1000
+ right_env = binary_right_env(expression, env)
1001
+ right_setup, right = prepare_expression_for_inline_lowering(expression.right, env: right_env, expected_type: operand_type || left_type)
1002
+
1003
+ unless %w[and or].include?(expression.operator)
1004
+ return [
1005
+ left_setup + right_setup,
1006
+ AST::BinaryOp.new(operator: expression.operator, left:, right:),
1007
+ ]
1008
+ end
1009
+
1010
+ return [[], expression] if left_setup.empty? && right_setup.empty?
1011
+
1012
+ result_type = infer_expression_type(expression, env:, expected_type:)
1013
+ result_name = fresh_c_temp_name(env, expression.operator)
1014
+ register_prepared_temp!(env, result_name, result_type)
1015
+ result_ref = IR::Name.new(name: result_name, type: result_type, pointer: false)
1016
+ left_value = lower_contextual_expression(left, env:, expected_type: result_type)
1017
+ right_value = lower_contextual_expression(right, env: right_env, expected_type: result_type)
1018
+ branch_condition = expression.operator == "and" ? result_ref : IR::Unary.new(operator: "not", operand: result_ref, type: @ctx.types.fetch("bool"))
1019
+
1020
+ [
1021
+ left_setup + [
1022
+ IR::LocalDecl.new(name: result_name, linkage_name: result_name, type: result_type, value: left_value),
1023
+ IR::IfStmt.new(
1024
+ condition: branch_condition,
1025
+ then_body: right_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: right_value)],
1026
+ else_body: nil,
1027
+ ),
1028
+ ],
1029
+ AST::Identifier.new(name: result_name),
1030
+ ]
1031
+ end
1032
+
1033
+ private def ensure_tuple_struct(tuple_type)
1034
+ return unless tuple_type.is_a?(Types::Tuple)
1035
+
1036
+ @registered_tuple_types ||= {}
1037
+ return if @registered_tuple_types[tuple_type]
1038
+
1039
+ linkage_name = tuple_type_name(tuple_type)
1040
+ return if @artifacts.synthetic_structs.any? { |s| s.linkage_name == linkage_name }
1041
+
1042
+ fields = tuple_type.element_types.each_with_index.map do |et, i|
1043
+ IR::Field.new(name: tuple_type.field_names[i], type: et)
1044
+ end
1045
+ @artifacts.synthetic_structs << IR::StructDecl.new(
1046
+ name: tuple_type.to_s,
1047
+ linkage_name: linkage_name,
1048
+ fields: fields,
1049
+ packed: false,
1050
+ alignment: nil,
1051
+ )
1052
+ @registered_tuple_types[tuple_type] = true
1053
+ end
1054
+
1055
+ private def tuple_type_name(type)
1056
+ sanitized = type.element_types.map { |et| sanitize_type_name_for_tuple(et) }.join("_")
1057
+ base = "mt_tuple_#{sanitized}"
1058
+ default_names = type.element_types.each_with_index.map { |_, i| "_#{i}" }
1059
+ if type.field_names != default_names
1060
+ base << "_" << type.field_names.map { |n| sanitize_type_name_for_tuple(n) }.join("_")
1061
+ end
1062
+ base
1063
+ end
1064
+
1065
+ private def sanitize_type_name_for_tuple(type)
1066
+ type.to_s.gsub(/[^a-zA-Z0-9]/, "_").gsub(/_+/, "_").gsub(/^_|_$/, "")
1067
+ end
1068
+
1069
+ def prepare_if_expression_for_inline_lowering(expression, env:, expected_type: nil)
1070
+ condition_setup, condition = prepare_expression_for_inline_lowering(expression.condition, env:, expected_type: @ctx.types.fetch("bool"))
1071
+ then_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: true, env:))
1072
+ else_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: false, env:))
1073
+ result_type = infer_expression_type(expression, env:, expected_type:)
1074
+ then_setup, then_expression = prepare_expression_for_inline_lowering(expression.then_expression, env: then_env, expected_type: result_type)
1075
+ else_setup, else_expression = prepare_expression_for_inline_lowering(expression.else_expression, env: else_env, expected_type: result_type)
1076
+
1077
+ return [[], expression] if condition_setup.empty? && then_setup.empty? && else_setup.empty?
1078
+
1079
+ result_name = fresh_c_temp_name(env, "if_expr")
1080
+ register_prepared_temp!(env, result_name, result_type)
1081
+ result_ref = IR::Name.new(name: result_name, type: result_type, pointer: false)
1082
+
1083
+ [
1084
+ condition_setup + [
1085
+ IR::LocalDecl.new(name: result_name, linkage_name: result_name, type: result_type, value: IR::ZeroInit.new(type: result_type)),
1086
+ IR::IfStmt.new(
1087
+ condition: lower_expression(condition, env:, expected_type: @ctx.types.fetch("bool")),
1088
+ then_body: then_setup + [
1089
+ IR::Assignment.new(
1090
+ target: result_ref,
1091
+ operator: "=",
1092
+ value: lower_contextual_expression(then_expression, env: then_env, expected_type: result_type),
1093
+ ),
1094
+ ],
1095
+ else_body: else_setup + [
1096
+ IR::Assignment.new(
1097
+ target: result_ref,
1098
+ operator: "=",
1099
+ value: lower_contextual_expression(else_expression, env: else_env, expected_type: result_type),
1100
+ ),
1101
+ ],
1102
+ ),
1103
+ ],
1104
+ AST::Identifier.new(name: result_name),
1105
+ ]
1106
+ end
1107
+
1108
+ def prepare_match_expression_for_inline_lowering(expression, env:, expected_type: nil)
1109
+ scrutinee_type = infer_expression_type(expression.expression, env:)
1110
+ expression_setup, prepared_expression = prepare_expression_for_inline_lowering(expression.expression, env:, expected_type: scrutinee_type)
1111
+ result_type = infer_expression_type(expression, env:, expected_type:)
1112
+ result_name = fresh_c_temp_name(env, "match_expr")
1113
+ register_prepared_temp!(env, result_name, result_type)
1114
+ result_ref = IR::Name.new(name: result_name, type: result_type, pointer: false)
1115
+ setup = expression_setup + [IR::LocalDecl.new(name: result_name, linkage_name: result_name, type: result_type, value: IR::ZeroInit.new(type: result_type))]
1116
+ lowered_expression = lower_expression(prepared_expression, env:, expected_type: scrutinee_type)
1117
+
1118
+ if scrutinee_type.is_a?(Types::Variant) &&
1119
+ expression.arms.any? { |arm| arm.binding_name && !wildcard_arm_pattern?(arm.pattern) } &&
1120
+ !duplicable_foreign_argument_expression?(lowered_expression)
1121
+ scrutinee_name = fresh_c_temp_name(env, "match_value")
1122
+ setup << IR::LocalDecl.new(name: scrutinee_name, linkage_name: scrutinee_name, type: scrutinee_type, value: lowered_expression)
1123
+ lowered_expression = IR::Name.new(name: scrutinee_name, type: scrutinee_type, pointer: false)
1124
+ end
1125
+
1126
+ switch_expression = lowered_expression
1127
+ string_if_chain = nil
1128
+ cases = if scrutinee_type.is_a?(Types::Variant)
1129
+ kind_type = @ctx.types.fetch("int")
1130
+ switch_expression = IR::Member.new(receiver: lowered_expression, member: "kind", type: kind_type)
1131
+ expression.arms.map do |arm|
1132
+ arm_env = duplicate_env(env)
1133
+ binding_decl = if arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
1134
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
1135
+ if arm_name && scrutinee_type.has_payload?(arm_name)
1136
+ fields = scrutinee_type.arm(arm_name)
1137
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
1138
+ data_expr = IR::Member.new(receiver: lowered_expression, member: "data", type: nil)
1139
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
1140
+ binding_c = c_local_name(arm.binding_name)
1141
+ arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: false, pointer: false)
1142
+ IR::LocalDecl.new(name: arm.binding_name, linkage_name: binding_c, type: payload_type, value: arm_expr)
1143
+ end
1144
+ end
1145
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1146
+ body = [binding_decl, *value_setup].compact
1147
+ body << IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))
1148
+ if wildcard_arm_pattern?(arm.pattern)
1149
+ IR::SwitchDefaultCase.new(body: body)
1150
+ else
1151
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
1152
+ IR::SwitchCase.new(value: IR::Name.new(name: enum_member_c_name(scrutinee_type, "kind_#{arm_name}"), type: kind_type, pointer: false), body: body)
1153
+ end
1154
+ end
1155
+ elsif scrutinee_type.is_a?(Types::StringView)
1156
+ bool_type = @ctx.types.fetch("bool")
1157
+ wildcard = expression.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
1158
+ non_wildcard = expression.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1159
+ else_body = if wildcard
1160
+ arm_env = duplicate_env(env)
1161
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(wildcard.value, env: arm_env, expected_type: result_type)
1162
+ value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1163
+ else
1164
+ []
1165
+ end
1166
+ non_wildcard.reverse_each do |arm|
1167
+ arm_env = duplicate_env(env)
1168
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1169
+ then_body = value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1170
+ lit = lower_expression(arm.pattern, env: arm_env, expected_type: scrutinee_type)
1171
+ cond = IR::Binary.new(operator: "==", left: lowered_expression, right: lit, type: bool_type)
1172
+ else_body = [IR::IfStmt.new(condition: cond, then_body:, else_body:)]
1173
+ end
1174
+ string_if_chain = else_body
1175
+ nil
1176
+ elsif scrutinee_type.is_a?(Types::Tuple)
1177
+ bool_type = @ctx.types.fetch("bool")
1178
+ wildcard = expression.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
1179
+ non_wildcard = expression.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1180
+ else_body = if wildcard
1181
+ arm_env = duplicate_env(env)
1182
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(wildcard.value, env: arm_env, expected_type: result_type)
1183
+ value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1184
+ else
1185
+ []
1186
+ end
1187
+ non_wildcard.reverse_each do |arm|
1188
+ arm_env = duplicate_env(env)
1189
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1190
+ then_body = value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1191
+ cond = tuple_arm_condition(arm, lowered_expression, scrutinee_type, bool_type, env: arm_env)
1192
+ else_body = [IR::IfStmt.new(condition: cond, then_body:, else_body:)]
1193
+ end
1194
+ string_if_chain = else_body
1195
+ nil
1196
+ else
1197
+ expression.arms.map do |arm|
1198
+ arm_env = duplicate_env(env)
1199
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1200
+ body = value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1201
+ if wildcard_arm_pattern?(arm.pattern)
1202
+ IR::SwitchDefaultCase.new(body: body)
1203
+ else
1204
+ IR::SwitchCase.new(value: lower_expression(arm.pattern, env: arm_env, expected_type: scrutinee_type), body: body)
1205
+ end
1206
+ end
1207
+ end
1208
+
1209
+ if string_if_chain
1210
+ [setup + string_if_chain, AST::Identifier.new(name: result_name)]
1211
+ else
1212
+ [setup + [IR::SwitchStmt.new(expression: switch_expression, cases: cases, exhaustive: true)], AST::Identifier.new(name: result_name)]
1213
+ end
1214
+ end
1215
+
1216
+ def tuple_arm_condition(arm, lowered_expression, scrutinee_type, bool_type, env:)
1217
+ elements = arm.pattern.elements
1218
+ element_types = scrutinee_type.element_types
1219
+ field_names = scrutinee_type.field_names
1220
+
1221
+ conds = elements.each_with_index.filter_map do |elem, index|
1222
+ next if elem.is_a?(AST::Identifier) && elem.name == "_"
1223
+
1224
+ field_expr = IR::Member.new(receiver: lowered_expression, member: field_names[index], type: element_types[index])
1225
+ lit = lower_expression(elem, env:, expected_type: element_types[index])
1226
+ IR::Binary.new(operator: "==", left: field_expr, right: lit, type: bool_type)
1227
+ end
1228
+
1229
+ conds.reduce { |acc, cond| IR::Binary.new(operator: "and", left: acc, right: cond, type: bool_type) }
1230
+ end
1231
+
1232
+ def materialize_prepared_expression(setup, value, env:, type:, prefix:)
1233
+ raise LoweringError, "cannot use void expression inline" unless value
1234
+
1235
+ if value.is_a?(IR::Name)
1236
+ register_prepared_temp!(env, value.name, value.type, pointer: value.pointer)
1237
+ return [setup, AST::Identifier.new(name: value.name)]
1238
+ end
1239
+
1240
+ temp_name = fresh_c_temp_name(env, prefix)
1241
+ register_prepared_temp!(env, temp_name, type)
1242
+ [
1243
+ setup + [IR::LocalDecl.new(name: temp_name, linkage_name: temp_name, type:, value:)],
1244
+ AST::Identifier.new(name: temp_name),
1245
+ ]
1246
+ end
1247
+
1248
+ def register_prepared_temp!(env, name, type, pointer: false, storage_type: nil, projection: nil, cstr_backed: false, cstr_list_backed: false)
1249
+ current_actual_scope(env[:scopes])[name] = local_binding(type:, storage_type:, linkage_name: name, mutable: false, pointer:, projection:, cstr_backed:, cstr_list_backed:)
1250
+ end
1251
+
1252
+ def foreign_call_requires_statement_lowering?(expression, binding, env:)
1253
+ return true if foreign_call_consumes_binding?(binding)
1254
+
1255
+ mapping_expression = foreign_mapping_expression(binding.ast)
1256
+ reference_counts = foreign_mapping_reference_counts(mapping_expression)
1257
+
1258
+ binding.ast.params.each_with_index do |param_ast, index|
1259
+ public_alias = param_ast.boundary_type ? foreign_mapping_public_alias_name(param_ast.name) : nil
1260
+ total_references = reference_counts.fetch(param_ast.name, 0)
1261
+ total_references += reference_counts.fetch(public_alias, 0) if public_alias
1262
+ next unless total_references > 1
1263
+ next if duplicable_foreign_argument_expression?(expression.arguments.fetch(index).value)
1264
+
1265
+ return true
1266
+ end
1267
+
1268
+ binding.ast.params.each_with_index do |param_ast, index|
1269
+ parameter = binding.type.params.fetch(index)
1270
+ next unless automatic_foreign_cstr_temp_needed?(parameter, expression.arguments.fetch(index).value, env:) ||
1271
+ automatic_foreign_cstr_list_temp_needed?(parameter, expression.arguments.fetch(index).value, env:)
1272
+
1273
+ return true
1274
+ end
1275
+
1276
+ expression.arguments.drop(binding.type.params.length).each do |argument|
1277
+ return true if automatic_variadic_foreign_cstr_temp_needed?(argument.value, env:)
1278
+ end
1279
+
1280
+ false
1281
+ end
1282
+
1283
+ def lower_expression(expression, env:, expected_type: nil)
1284
+ type = infer_expression_type(expression, env:, expected_type:)
1285
+
1286
+ case expression
1287
+ when AST::AwaitExpr
1288
+ raise LoweringError, "await expressions must be lowered in async statement context"
1289
+ when AST::IntegerLiteral
1290
+ IR::IntegerLiteral.new(value: expression.value, type:)
1291
+ when AST::CharLiteral
1292
+ IR::IntegerLiteral.new(value: expression.value, type:)
1293
+ when AST::FloatLiteral
1294
+ IR::FloatLiteral.new(value: expression.value, type:)
1295
+ when AST::SizeofExpr
1296
+ target_type = resolve_type_ref_with_fallback(expression.type, env:)
1297
+ target_type ? IR::SizeofExpr.new(target_type:, type:) : raise(LoweringError, "size_of argument is not a concrete type")
1298
+ when AST::AlignofExpr
1299
+ target_type = resolve_type_ref_with_fallback(expression.type, env:)
1300
+ target_type ? IR::AlignofExpr.new(target_type:, type:) : raise(LoweringError, "align_of argument is not a concrete type")
1301
+ when AST::OffsetofExpr
1302
+ target_type = resolve_type_ref(expression.type)
1303
+ if !@bypass_sema_type_cache && (precomputed = @ctx.const_values[@ctx.ast.node_ids[expression.object_id]])
1304
+ IR::IntegerLiteral.new(value: precomputed, type:)
1305
+ elsif (binding = lookup_value(expression.field, env)) && binding[:const_value].is_a?(Types::FieldHandle)
1306
+ IR::OffsetofExpr.new(target_type:, field: binding[:const_value].field_name, type:)
1307
+ else
1308
+ IR::OffsetofExpr.new(target_type:, field: expression.field, type:)
1309
+ end
1310
+ when AST::StringLiteral
1311
+ IR::StringLiteral.new(value: expression.value, type:, cstring: expression.cstring)
1312
+ when AST::FormatString
1313
+ raise LoweringError, "unprepared format string reached raw lowering; format strings should be materialized before direct lowering"
1314
+ when AST::BooleanLiteral
1315
+ IR::BooleanLiteral.new(value: expression.value, type:)
1316
+ when AST::NullLiteral
1317
+ IR::NullLiteral.new(type:)
1318
+ when AST::Identifier
1319
+ binding = lookup_value(expression.name, env)
1320
+ if binding
1321
+ lower_bound_identifier(binding, expected_type:)
1322
+ elsif @ctx.functions.key?(expression.name)
1323
+ function_binding = @ctx.functions.fetch(expression.name)
1324
+ raise LoweringError, "generic function #{expression.name} cannot be used as a value" if function_binding.type_params.any?
1325
+ raise LoweringError, "foreign function #{expression.name} cannot be used as a value" if foreign_function_binding?(function_binding)
1326
+
1327
+ IR::Name.new(name: function_binding_c_name(function_binding, module_name: @ctx.module_name), type: type, pointer: false)
1328
+ else
1329
+ raise LoweringError, "unsupported identifier #{expression.name}"
1330
+ end
1331
+ when AST::MemberAccess
1332
+ lower_member_access(expression, env:, type:)
1333
+ when AST::IndexAccess
1334
+ receiver_type = infer_expression_type(expression.receiver, env:)
1335
+ receiver = lower_expression(expression.receiver, env:)
1336
+ index = lower_expression(expression.index, env:)
1337
+ if array_type?(receiver_type) && addressable_storage_expression?(expression.receiver)
1338
+ IR::CheckedIndex.new(receiver:, index:, receiver_type:, type:)
1339
+ elsif receiver_type.is_a?(Types::Span)
1340
+ IR::CheckedSpanIndex.new(receiver:, index:, receiver_type:, type:)
1341
+ else
1342
+ IR::Index.new(receiver:, index:, type:)
1343
+ end
1344
+ when AST::UnaryOp
1345
+ raise LoweringError, "propagation expressions must be prepared before direct lowering" if expression.operator == "?"
1346
+
1347
+ operand = lower_expression(expression.operand, env:, expected_type: type)
1348
+ expanded = lower_vector_unary_op(expression.operator, operand, type)
1349
+ return expanded if expanded
1350
+
1351
+ IR::Unary.new(operator: expression.operator, operand:, type:)
1352
+ when AST::BinaryOp
1353
+ right_env = binary_right_env(expression, env)
1354
+ left_type, right_type = infer_binary_operand_types(expression, env:, expected_type: type)
1355
+ operand_type = promoted_binary_operand_type(expression.operator, left_type, right_type)
1356
+ left = lower_expression(expression.left, env:, expected_type: operand_type || type)
1357
+ right = lower_expression(expression.right, env: right_env, expected_type: operand_type || left.type)
1358
+ left = cast_expression(left, operand_type) if operand_type
1359
+ right = cast_expression(right, operand_type) if operand_type
1360
+
1361
+ expanded = lower_vector_binary_op(expression.operator, left, left_type, right, right_type, type)
1362
+ return expanded if expanded
1363
+
1364
+ IR::Binary.new(operator: expression.operator, left:, right:, type:)
1365
+ when AST::IfExpr
1366
+ then_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: true, env:))
1367
+ else_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: false, env:))
1368
+ IR::Conditional.new(
1369
+ condition: lower_expression(expression.condition, env:, expected_type: @ctx.types.fetch("bool")),
1370
+ then_expression: lower_contextual_expression(expression.then_expression, env: then_env, expected_type: type),
1371
+ else_expression: lower_contextual_expression(expression.else_expression, env: else_env, expected_type: type),
1372
+ type:,
1373
+ )
1374
+ when AST::MatchExpr
1375
+ raise LoweringError, "match expressions must be prepared before direct lowering"
1376
+ when AST::UnsafeExpr
1377
+ lower_expression(expression.expression, env:, expected_type: type)
1378
+ when AST::ProcExpr
1379
+ proc_type = type.is_a?(Types::Proc) ? type : infer_expression_type(expression, env:, expected_type: type)
1380
+ _setup, value = lower_proc_expression_for_local(expression, env:, local_name: fresh_c_temp_name(env, "proc_expr"), proc_type: proc_type)
1381
+ value
1382
+ when AST::DetachExpr
1383
+ lower_detach_expr(expression, env:)
1384
+ when AST::Call
1385
+ lower_call(expression, env:, type:)
1386
+ when AST::PrefixCast
1387
+ lowered_arg = lower_expression(expression.expression, env:)
1388
+ IR::Cast.new(target_type: type, expression: lowered_arg, type:)
1389
+ when AST::Specialization
1390
+ lower_specialization(expression, env:, type:)
1391
+ when AST::ExpressionList
1392
+ if array_type?(type) || array_type?(expected_type)
1393
+ lowered_elements = expression.elements.map do |element|
1394
+ if element.is_a?(AST::Argument)
1395
+ lower_expression(element.value, env:, expected_type: array_element_type(expected_type || type))
1396
+ else
1397
+ lower_expression(element, env:, expected_type: array_element_type(expected_type || type))
1398
+ end
1399
+ end
1400
+ IR::ArrayLiteral.new(type: expected_type || type, elements: lowered_elements)
1401
+ else
1402
+ ensure_tuple_struct(type)
1403
+ fields = expression.elements.each_with_index.map do |element, index|
1404
+ if element.is_a?(AST::Argument)
1405
+ element_type = type.element_types[index]
1406
+ field_name = element.name
1407
+ IR::AggregateField.new(name: field_name, value: lower_expression(element.value, env:, expected_type: element_type))
1408
+ else
1409
+ element_type = type.element_types[index]
1410
+ IR::AggregateField.new(name: "_#{index}", value: lower_expression(element, env:, expected_type: element_type))
1411
+ end
1412
+ end
1413
+ IR::AggregateLiteral.new(type:, fields:)
1414
+ end
1415
+ else
1416
+ raise LoweringError, "unsupported expression #{expression.class.name}"
1417
+ end
1418
+ end
1419
+
1420
+ def lower_member_access(expression, env:, type:)
1421
+ if (type_expr = resolve_type_expression(expression.receiver))
1422
+ if type_expr.is_a?(Types::Variant)
1423
+ return IR::VariantLiteral.new(type: type_expr, arm_name: expression.member, fields: [])
1424
+ end
1425
+
1426
+ member_name = if (type_expr.is_a?(Types::Enum) || type_expr.is_a?(Types::Flags)) && !type_expr.external
1427
+ enum_member_c_name(type_expr, expression.member)
1428
+ else
1429
+ expression.member
1430
+ end
1431
+ return IR::Name.new(name: member_name, type:, pointer: false)
1432
+ end
1433
+
1434
+ if expression.receiver.is_a?(AST::Identifier) && @ctx.imports.key?(expression.receiver.name)
1435
+ imported_module = @ctx.imports.fetch(expression.receiver.name)
1436
+ if imported_module.functions.key?(expression.member)
1437
+ function_binding = imported_module.functions.fetch(expression.member)
1438
+ raise LoweringError, "generic function #{expression.receiver.name}.#{expression.member} cannot be used as a value" if function_binding.type_params.any?
1439
+ raise LoweringError, "foreign function #{expression.receiver.name}.#{expression.member} cannot be used as a value" if foreign_function_binding?(function_binding)
1440
+
1441
+ return IR::Name.new(name: function_binding_c_name(function_binding, module_name: imported_module.name), type:, pointer: false)
1442
+ end
1443
+
1444
+ return IR::Name.new(name: imported_value_c_name(imported_module, expression.member), type:, pointer: false)
1445
+ end
1446
+
1447
+ receiver_type = infer_expression_type(expression.receiver, env:)
1448
+
1449
+ if expression.receiver.is_a?(AST::IndexAccess)
1450
+ base_type = infer_expression_type(expression.receiver.receiver, env:)
1451
+ if base_type.is_a?(Types::SoA)
1452
+ return lower_soa_indexed_field_access(expression.receiver.receiver, expression.receiver.index, expression.member, base_type, env:, type:)
1453
+ end
1454
+ end
1455
+
1456
+ if receiver_type == @ctx.types["field_handle"]
1457
+ handle = compile_time_const_value(expression.receiver, env:)
1458
+ return lower_compile_time_handle_member(handle, expression.member, type) if handle.is_a?(Types::FieldHandle)
1459
+ end
1460
+ if receiver_type == @ctx.types["member_handle"]
1461
+ handle = compile_time_const_value(expression.receiver, env:)
1462
+ return lower_compile_time_handle_member(handle, expression.member, type) if handle.is_a?(Types::MemberHandle)
1463
+ end
1464
+
1465
+ if array_type?(receiver_type) && expression.member == "as_span"
1466
+ return lower_array_to_span_expression(lower_expression(expression.receiver, env:), type)
1467
+ end
1468
+
1469
+ receiver = lower_expression(expression.receiver, env:)
1470
+ member_expr = IR::Member.new(receiver:, member: member_c_name(receiver_type, expression.member), type:)
1471
+ if self_referencing_variant_field_access?(receiver_type, expression.member, type)
1472
+ return IR::Unary.new(operator: "*", operand: member_expr, type:)
1473
+ end
1474
+ member_expr
1475
+ end
1476
+
1477
+ def self_referencing_variant_field_access?(receiver_type, member, field_type)
1478
+ return false unless receiver_type.is_a?(Types::VariantArmPayload)
1479
+
1480
+ outer = receiver_type.variant_type
1481
+ return field_type == outer if outer.is_a?(Types::Variant)
1482
+ return field_type == outer if outer.is_a?(Types::VariantInstance)
1483
+
1484
+ false
1485
+ end
1486
+
1487
+ def lower_compile_time_handle_member(handle, member, type)
1488
+ case handle
1489
+ when Types::FieldHandle
1490
+ case member
1491
+ when "name" then IR::StringLiteral.new(value: handle.field_name, type: @ctx.types["str"], cstring: false)
1492
+ when "type" then nil # handled via compile_time_const_value before lowering
1493
+ end
1494
+ when Types::MemberHandle
1495
+ case member
1496
+ when "name" then IR::StringLiteral.new(value: handle.member_name, type: @ctx.types["str"], cstring: false)
1497
+ when "value"
1498
+ value_type = @ctx.types["int"]
1499
+ IR::IntegerLiteral.new(value: handle.member_value || 0, type: value_type)
1500
+ end
1501
+ end
1502
+ end
1503
+
1504
+ def lower_vector_binary_op(operator, left, left_type, right, right_type, result_type)
1505
+ return nil unless result_type.is_a?(Types::Vector) || result_type.is_a?(Types::Matrix) || result_type.is_a?(Types::Quaternion)
1506
+
1507
+ if result_type.is_a?(Types::Vector)
1508
+ return lower_vector_binary_op_on_vectors(operator, left, left_type, right, right_type, result_type)
1509
+ end
1510
+
1511
+ return lower_aggregate_binary_op(operator, left, right, result_type) if operator == "+" || operator == "-"
1512
+
1513
+ return lower_aggregate_binary_op(operator, left, right, result_type) if result_type.is_a?(Types::Quaternion)
1514
+
1515
+ scalar_is_left = !left_type.is_a?(Types::Matrix)
1516
+ aggregate_expr = scalar_is_left ? right : left
1517
+ scalar_expr = scalar_is_left ? left : right
1518
+ scalar_type = scalar_is_left ? left_type : right_type
1519
+
1520
+ fields = result_type.fields.map do |fname, ftype|
1521
+ field_expr = IR::Member.new(receiver: aggregate_expr, member: fname, type: ftype)
1522
+ left_expr = scalar_is_left ? scalar_expr : field_expr
1523
+ right_expr = scalar_is_left ? field_expr : scalar_expr
1524
+ value = lower_vector_binary_op(operator, left_expr, ftype, right_expr, scalar_type, ftype) ||
1525
+ IR::Binary.new(operator:, left: left_expr, right: right_expr, type: ftype)
1526
+ IR::AggregateField.new(name: fname, value:)
1527
+ end
1528
+ IR::AggregateLiteral.new(fields:, type: result_type)
1529
+ end
1530
+
1531
+ def lower_vector_unary_op(operator, operand, result_type)
1532
+ return nil unless result_type.is_a?(Types::Vector) || result_type.is_a?(Types::Matrix) || result_type.is_a?(Types::Quaternion)
1533
+ return nil unless operator == "+" || operator == "-"
1534
+
1535
+ fields = result_type.fields.map do |fname, ftype|
1536
+ field_expr = IR::Member.new(receiver: operand, member: fname, type: ftype)
1537
+ value = lower_vector_unary_op(operator, field_expr, ftype) || IR::Unary.new(operator:, operand: field_expr, type: ftype)
1538
+ IR::AggregateField.new(name: fname, value:)
1539
+ end
1540
+ IR::AggregateLiteral.new(fields:, type: result_type)
1541
+ end
1542
+
1543
+ private
1544
+
1545
+ def lower_vector_binary_op_on_vectors(operator, left, left_type, right, right_type, result_type)
1546
+ if left_type.is_a?(Types::Vector) && right_type.is_a?(Types::Vector)
1547
+ return lower_aggregate_binary_op(operator, left, right, result_type)
1548
+ end
1549
+
1550
+ return nil unless operator == "*" || operator == "/"
1551
+
1552
+ scalar_is_left = !left_type.is_a?(Types::Vector)
1553
+ vector_expr = scalar_is_left ? right : left
1554
+ scalar_expr = scalar_is_left ? left : right
1555
+
1556
+ fields = result_type.fields.map do |fname, ftype|
1557
+ field_expr = IR::Member.new(receiver: vector_expr, member: fname, type: ftype)
1558
+ left_expr = scalar_is_left ? scalar_expr : field_expr
1559
+ right_expr = scalar_is_left ? field_expr : scalar_expr
1560
+ value = IR::Binary.new(operator:, left: left_expr, right: right_expr, type: ftype)
1561
+ IR::AggregateField.new(name: fname, value:)
1562
+ end
1563
+ IR::AggregateLiteral.new(fields:, type: result_type)
1564
+ end
1565
+
1566
+ def lower_aggregate_binary_op(operator, left, right, result_type)
1567
+ fields = result_type.fields.map do |fname, ftype|
1568
+ left_field = IR::Member.new(receiver: left, member: fname, type: ftype)
1569
+ right_field = IR::Member.new(receiver: right, member: fname, type: ftype)
1570
+ value = lower_vector_binary_op(operator, left_field, ftype, right_field, ftype, ftype) || IR::Binary.new(
1571
+ operator:,
1572
+ left: left_field,
1573
+ right: right_field,
1574
+ type: ftype,
1575
+ )
1576
+ IR::AggregateField.new(name: fname, value:)
1577
+ end
1578
+ IR::AggregateLiteral.new(fields:, type: result_type)
1579
+ end
1580
+
1581
+ def lower_soa_indexed_field_access(soa_base, index_expr, field_name, soa_type, env:, type:)
1582
+ field_type = soa_type.fields[field_name]
1583
+ raise LoweringError, "SoA type #{soa_type} has no field #{field_name}" unless field_type
1584
+
1585
+ receiver = lower_expression(soa_base, env:)
1586
+ index = lower_expression(index_expr, env:)
1587
+ IR::Index.new(
1588
+ receiver: IR::Member.new(receiver:, member: field_name, type: field_type),
1589
+ index:,
1590
+ type:,
1591
+ )
1592
+ end
1593
+
1594
+ def member_c_name(receiver_type, member)
1595
+ owner_type = receiver_type
1596
+ loop do
1597
+ case owner_type
1598
+ when Types::Nullable
1599
+ owner_type = owner_type.base
1600
+ when Types::GenericInstance
1601
+ if %w[ptr const_ptr own ref].include?(owner_type.name) && owner_type.arguments.length == 1
1602
+ owner_type = owner_type.arguments.first
1603
+ else
1604
+ break
1605
+ end
1606
+ else
1607
+ break
1608
+ end
1609
+ end
1610
+
1611
+ if (event_type = event_member_from_owner_type(owner_type, member))
1612
+ return event_type.hidden_field_name
1613
+ end
1614
+
1615
+ owner_type.field_c_name(member)
1616
+ end
1617
+
1618
+ def resolve_type_ref_with_fallback(type_ref, env:)
1619
+ resolve_type_ref(type_ref)
1620
+ rescue LoweringError
1621
+ return unless type_ref.name.parts.length >= 1
1622
+
1623
+ expression = build_expression_from_qualified_name(type_ref.name)
1624
+ return unless expression
1625
+
1626
+ ct_value = compile_time_const_value(expression, env:)
1627
+ if ct_value.is_a?(Types::Struct) || ct_value.is_a?(Types::Primitive) ||
1628
+ ct_value.is_a?(Types::Union) || ct_value.is_a?(Types::Nullable) ||
1629
+ ct_value.is_a?(Types::StructInstance)
1630
+ ct_value
1631
+ end
1632
+ end
1633
+
1634
+ def build_expression_from_qualified_name(qualified_name)
1635
+ parts = qualified_name.parts
1636
+ return unless parts.length >= 1
1637
+
1638
+ expr = AST::Identifier.new(name: parts.first)
1639
+ parts[1..].each do |part|
1640
+ expr = AST::MemberAccess.new(receiver: expr, member: part)
1641
+ end
1642
+ expr
1643
+ end
1644
+ end
1645
+ end