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,1052 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LowererBlock
5
+ private
6
+
7
+
8
+ def lower_block(statements, env:, active_defers:, return_type:, loop_flow:, allow_return: true)
9
+ local_env = duplicate_env(env)
10
+ lowered = []
11
+ local_defers = []
12
+ local_env[:return_context] = {
13
+ return_type:,
14
+ active_defers:,
15
+ local_defers:,
16
+ allow_return:,
17
+ }
18
+
19
+ statements.each do |statement|
20
+ case statement
21
+ when AST::DeferStmt
22
+ local_defers << if statement.body
23
+ lower_defer_cleanup_body(statement.body, env: local_env, return_type:)
24
+ else
25
+ lower_defer_cleanup_expression(statement.expression, env: local_env)
26
+ end
27
+ when AST::UnsafeStmt
28
+ body = lower_block(
29
+ statement.body,
30
+ env: local_env,
31
+ active_defers: active_defers + local_defers,
32
+ return_type:,
33
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
34
+ allow_return:,
35
+ )
36
+ lowered << IR::BlockStmt.new(body:)
37
+ when AST::LocalDecl
38
+ if statement.destructure_bindings
39
+ lower_destructure_decl(statement, env: local_env, lowered:, local_defers:, active_defers:, return_type:, loop_flow:, allow_return:)
40
+ next
41
+ end
42
+
43
+ storage_type = if statement.else_body
44
+ infer_expression_type(statement.value, env: local_env)
45
+ elsif statement.type
46
+ resolve_type_ref(statement.type)
47
+ else
48
+ infer_expression_type(statement.value, env: local_env)
49
+ end
50
+ type = if statement.else_body
51
+ statement.type ? resolve_type_ref(statement.type) : let_else_success_type(storage_type)
52
+ else
53
+ storage_type
54
+ end
55
+ linkage_name = let_else_storage_c_name(statement, local_env)
56
+ decl_name = bind_let_else_local?(statement) ? statement.name : linkage_name
57
+ prepared_setup = []
58
+ prepared_value = statement.value
59
+ prepared_cleanups = []
60
+ emitted_decl = false
61
+ if statement.value
62
+ local_env[:current_local_name] = linkage_name
63
+ prepared_setup, prepared_value, prepared_cleanups = prepare_expression_with_cleanups(
64
+ statement.value,
65
+ env: local_env,
66
+ expected_type: storage_type,
67
+ allow_root_statement_foreign: true,
68
+ materialize_array_calls: !array_type?(storage_type),
69
+ )
70
+ lowered.concat(prepared_setup)
71
+ end
72
+ if prepared_value && (foreign_call = foreign_call_info(prepared_value, local_env))
73
+ setup, value, call_type, release_assignments, cleanup_statements = lower_foreign_call_components(
74
+ foreign_call,
75
+ env: local_env,
76
+ expected_type: storage_type,
77
+ statement_position: false,
78
+ )
79
+ lowered.concat(setup)
80
+ raise LoweringError, "foreign call used to initialize #{statement.name} must return a value" if call_type == @ctx.types.fetch("void")
81
+ raise LoweringError, "consuming foreign calls must return void" unless release_assignments.empty?
82
+
83
+ lowered << IR::LocalDecl.new(name: decl_name, linkage_name:, type: storage_type, value:, line: statement.line, source_path: @ctx.current_analysis_path)
84
+ lowered.concat(cleanup_statements)
85
+ emitted_decl = true
86
+ elsif prepared_value.is_a?(AST::ProcExpr)
87
+ setup, value = lower_proc_expression_for_local(prepared_value, env: local_env, local_name: statement.name, proc_type: storage_type)
88
+ lowered.concat(setup)
89
+ elsif prepared_value
90
+ value = lower_contextual_expression(
91
+ prepared_value,
92
+ env: local_env,
93
+ expected_type: storage_type,
94
+ contextual_int_to_float: statement.type && contextual_int_to_float_target?(type),
95
+ )
96
+ else
97
+ value = IR::ZeroInit.new(type: storage_type)
98
+ end
99
+ if value && storage_type.is_a?(Types::Nullable) && !value.type.is_a?(Types::Nullable) && !pointer_like_type?(storage_type.base)
100
+ value = nullable_some_literal(storage_type, value)
101
+ end
102
+ if bind_let_else_local?(statement)
103
+ current_actual_scope(local_env[:scopes])[statement.name] = local_binding(
104
+ type:,
105
+ storage_type:,
106
+ linkage_name:,
107
+ mutable: statement.kind == :var,
108
+ pointer: false,
109
+ projection: statement.else_body ? let_else_binding_projection(storage_type) : nil,
110
+ cstr_backed: cstr_backed_storage_value?(storage_type, prepared_value, local_env),
111
+ cstr_list_backed: cstr_list_backed_storage_value?(storage_type, prepared_value, local_env),
112
+ const_value: statement.else_body ? nil : statement.kind == :let && prepared_value ? compile_time_const_value(prepared_value, env: local_env) : nil,
113
+ )
114
+ end
115
+ lowered << IR::LocalDecl.new(name: decl_name, linkage_name:, type: storage_type, value:, line: statement.line, source_path: @ctx.current_analysis_path) unless emitted_decl
116
+ if statement.else_body
117
+ else_env = if statement.else_binding
118
+ duplicate_env(local_env).tap do |env_with_error|
119
+ current_actual_scope(env_with_error[:scopes])[statement.else_binding.name] = local_binding(
120
+ type: let_else_error_type(storage_type),
121
+ storage_type:,
122
+ linkage_name:,
123
+ mutable: false,
124
+ pointer: false,
125
+ projection: :result_failure_error,
126
+ )
127
+ end
128
+ else
129
+ local_env
130
+ end
131
+ else_body = lower_block(
132
+ statement.else_body,
133
+ env: else_env,
134
+ active_defers: active_defers + local_defers + prepared_cleanups,
135
+ return_type:,
136
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
137
+ allow_return:,
138
+ )
139
+ local_ref = IR::Name.new(name: linkage_name, type: storage_type, pointer: false)
140
+ lowered << IR::IfStmt.new(
141
+ condition: let_else_failure_condition(local_ref, storage_type),
142
+ then_body: else_body,
143
+ else_body: nil,
144
+ )
145
+ end
146
+ local_defers.concat(prepared_cleanups)
147
+ if contains_proc_storage_type?(storage_type)
148
+ local_value = IR::Name.new(name: linkage_name, type: storage_type, pointer: false)
149
+ # Use guarded release so zero-initialized var locals are safe (invoke == NULL guard).
150
+ local_defers << lower_proc_contained_guarded_release_statements(local_value, storage_type)
151
+ if statement.value && !expression_contains_proc_expr?(statement.value)
152
+ lowered.concat(lower_proc_contained_retain_statements(local_value, storage_type))
153
+ end
154
+ end
155
+ when AST::Assignment
156
+ if statement.operator == "=" &&
157
+ statement.target.is_a?(AST::IndexAccess) &&
158
+ statement.target.index.is_a?(AST::RangeExpr) &&
159
+ statement.value.is_a?(AST::ExpressionList)
160
+ lowered.concat(lower_range_index_assignment(statement, env: local_env))
161
+ next
162
+ end
163
+ target = lower_assignment_target(statement.target, env: local_env)
164
+ prepared_cleanups = []
165
+ prepared_setup, prepared_value, prepared_cleanups = prepare_expression_with_cleanups(
166
+ statement.value,
167
+ env: local_env,
168
+ expected_type: target.type,
169
+ allow_root_statement_foreign: true,
170
+ materialize_array_calls: !array_type?(target.type),
171
+ )
172
+ lowered.concat(prepared_setup)
173
+ if (foreign_call = foreign_call_info(prepared_value, local_env))
174
+ setup, value, call_type, release_assignments, cleanup_statements = lower_foreign_call_components(
175
+ foreign_call,
176
+ env: local_env,
177
+ expected_type: target.type,
178
+ statement_position: false,
179
+ )
180
+ lowered.concat(setup)
181
+ raise LoweringError, "foreign call used in assignment must return a value" if call_type == @ctx.types.fetch("void")
182
+ raise LoweringError, "consuming foreign calls must return void" unless release_assignments.empty?
183
+
184
+ lowered << IR::Assignment.new(target:, operator: statement.operator, value:)
185
+ lowered.concat(cleanup_statements)
186
+ update_cstr_metadata_for_assignment!(statement, prepared_value, local_env)
187
+ local_defers.concat(suppress_format_releases_for_assignment(prepared_cleanups, target.type))
188
+ next
189
+ else
190
+ value = if statement.operator == "="
191
+ lower_contextual_expression(
192
+ prepared_value,
193
+ env: local_env,
194
+ expected_type: target.type,
195
+ external_numeric: external_numeric_assignment_target?(statement.target, env: local_env),
196
+ contextual_int_to_float: contextual_int_to_float_target?(target.type),
197
+ )
198
+ elsif ["+=", "-=", "*=", "/="].include?(statement.operator)
199
+ lower_contextual_expression(
200
+ prepared_value,
201
+ env: local_env,
202
+ expected_type: target.type,
203
+ contextual_int_to_float: contextual_int_to_float_target?(target.type),
204
+ )
205
+ else
206
+ lower_expression(prepared_value, env: local_env, expected_type: target.type)
207
+ end
208
+ end
209
+ if target.type.is_a?(Types::Nullable) && value && !value.type.is_a?(Types::Nullable) && !pointer_like_type?(target.type.base)
210
+ value = nullable_some_literal(target.type, value)
211
+ end
212
+ update_cstr_metadata_for_assignment!(statement, prepared_value, local_env)
213
+ local_defers.concat(suppress_format_releases_for_assignment(prepared_cleanups, target.type))
214
+ if statement.operator == "=" && contains_proc_storage_type?(target.type)
215
+ # Materialize the RHS to a C temp to avoid evaluating aggregate literals multiple times
216
+ # and to ensure retain/release operate on a stable struct value throughout the sequence.
217
+ rhs_name = fresh_c_temp_name(local_env, "proc_assign")
218
+ lowered << IR::LocalDecl.new(name: rhs_name, linkage_name: rhs_name, type: target.type, value:)
219
+ rhs = IR::Name.new(name: rhs_name, type: target.type, pointer: false)
220
+ # Retain proc fields in the incoming value that are NOT from fresh proc expressions
221
+ # (fresh proc exprs carry refcount=1 and transfer ownership; existing procs need +1).
222
+ lowered.concat(lower_proc_selective_retain_statements(rhs, statement.value, target.type))
223
+ # Release old proc fields in the target (guarded: target may be zero-initialized).
224
+ lowered.concat(lower_proc_contained_guarded_release_statements(target, target.type))
225
+ lowered << IR::Assignment.new(target:, operator: "=", value: rhs)
226
+ else
227
+ lowered << IR::Assignment.new(target:, operator: statement.operator, value:)
228
+ end
229
+ when AST::IfStmt
230
+ if statement.inline
231
+ lowered.concat(lower_inline_if_stmt(statement, env: local_env, active_defers:, return_type:, allow_return:))
232
+ next
233
+ end
234
+
235
+ false_refinements = {}
236
+ branch_entries = []
237
+
238
+ # When inside inline for, try to const-evaluate conditions
239
+ unless statement.branches.empty?
240
+ if @bypass_sema_type_cache
241
+ ct_cond = compile_time_const_value(statement.branches.first.condition, env: local_env)
242
+ if ct_cond == true
243
+ lowered.concat(lower_block(
244
+ statement.branches.first.body,
245
+ env: local_env,
246
+ active_defers: active_defers + local_defers,
247
+ return_type:,
248
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
249
+ allow_return:,
250
+ ))
251
+ next
252
+ elsif ct_cond == false
253
+ if statement.else_body
254
+ lowered.concat(lower_block(
255
+ statement.else_body,
256
+ env: local_env,
257
+ active_defers: active_defers + local_defers,
258
+ return_type:,
259
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
260
+ allow_return:,
261
+ ))
262
+ end
263
+ next
264
+ end
265
+ end
266
+ end
267
+
268
+ statement.branches.each do |branch|
269
+ branch_env = env_with_refinements(local_env, false_refinements)
270
+ condition_setup, prepared_condition, condition_cleanups = prepare_expression_with_cleanups(
271
+ branch.condition,
272
+ env: branch_env,
273
+ expected_type: @ctx.types.fetch("bool"),
274
+ )
275
+ true_refinements = merge_refinements(false_refinements, flow_refinements(branch.condition, truthy: true, env: branch_env))
276
+
277
+ branch_entries << [
278
+ condition_setup,
279
+ condition_cleanups,
280
+ lower_expression(prepared_condition, env: branch_env, expected_type: @ctx.types.fetch("bool")),
281
+ lower_block(
282
+ branch.body,
283
+ env: env_with_refinements(local_env, true_refinements),
284
+ active_defers: active_defers + local_defers,
285
+ return_type:,
286
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
287
+ allow_return:,
288
+ ),
289
+ ]
290
+
291
+ false_refinements = merge_refinements(false_refinements, flow_refinements(branch.condition, truthy: false, env: branch_env))
292
+ end
293
+
294
+ nested_else_body = statement.else_body ? lower_block(
295
+ statement.else_body,
296
+ env: env_with_refinements(local_env, false_refinements),
297
+ active_defers: active_defers + local_defers,
298
+ return_type:,
299
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
300
+ allow_return:,
301
+ ) : []
302
+
303
+ nested_if = nested_else_body
304
+ branch_entries.reverse_each do |condition_setup, condition_cleanups, condition, then_body|
305
+ condition_cleanup_statements = condition_cleanups.flat_map(&:itself)
306
+ nested_if = [
307
+ *condition_setup,
308
+ IR::IfStmt.new(
309
+ condition:,
310
+ then_body: condition_cleanup_statements + then_body,
311
+ else_body: condition_cleanup_statements + nested_if,
312
+ ),
313
+ ]
314
+ end
315
+ lowered.concat(nested_if)
316
+
317
+ merge_cstr_metadata_after_if_statement!(statement, local_env)
318
+
319
+ if statement.else_body.nil? && statement.branches.all? { |branch| cfg_block_always_terminates?(branch.body) }
320
+ local_env[:scopes] = scopes_with_refinements(local_env[:scopes], false_refinements)
321
+ end
322
+ when AST::MatchStmt
323
+ if statement.inline
324
+ lowered.concat(lower_inline_match_stmt(statement, env: local_env, active_defers:, return_type:, allow_return:))
325
+ else
326
+ scrutinee_type = infer_expression_type(statement.expression, env: local_env)
327
+ expression_setup, prepared_expression, expression_cleanups = prepare_expression_with_cleanups(
328
+ statement.expression,
329
+ env: local_env,
330
+ expected_type: scrutinee_type,
331
+ )
332
+ lowered.concat(expression_setup)
333
+ expression = lower_expression(prepared_expression, env: local_env, expected_type: scrutinee_type)
334
+
335
+ if scrutinee_type.is_a?(Types::Variant) &&
336
+ statement.arms.any? { |arm| arm.binding_name && !wildcard_arm_pattern?(arm.pattern) } &&
337
+ !duplicable_foreign_argument_expression?(expression)
338
+ scrutinee_linkage_name = fresh_c_temp_name(local_env, "match_value")
339
+ lowered << IR::LocalDecl.new(name: scrutinee_linkage_name, linkage_name: scrutinee_linkage_name, type: scrutinee_type, value: expression)
340
+ expression = IR::Name.new(name: scrutinee_linkage_name, type: scrutinee_type, pointer: false)
341
+ end
342
+
343
+ if scrutinee_type.is_a?(Types::Variant)
344
+ if statement.arms.any? { |arm| arm.pattern.is_a?(AST::Call) }
345
+ kind_type = @ctx.types.fetch("int")
346
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
347
+ @match_label_counter ||= 0
348
+ @match_label_counter += 1
349
+ m = @match_label_counter
350
+ match_end_label = "__mt_match_#{m}_end"
351
+ arm_next_label = "__mt_match_#{m}_arm_next"
352
+
353
+ statement.arms.each_with_index do |arm, arm_index|
354
+ arm_local_env = duplicate_env(local_env)
355
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern) unless wildcard_arm_pattern?(arm.pattern)
356
+
357
+ # Emit label for all arms except the first (catches previous arm's goto)
358
+ if arm_index > 0
359
+ lowered << IR::LabelStmt.new(name: "__mt_match_#{m}_arm_#{arm_index}")
360
+ end
361
+
362
+ if arm_name && !wildcard_arm_pattern?(arm.pattern)
363
+ tag_value = IR::Name.new(name: enum_member_c_name(scrutinee_type, "kind_#{arm_name}"), type: kind_type, pointer: false)
364
+ tag_expr = IR::Member.new(receiver: expression, member: "kind", type: kind_type)
365
+ goto_label = if arm_index < statement.arms.length - 1
366
+ "__mt_match_#{m}_arm_#{arm_index + 1}"
367
+ else
368
+ arm_next_label
369
+ end
370
+ tag_check = IR::Binary.new(operator: "!=", left: tag_expr, right: tag_value, type: @ctx.types.fetch("bool"))
371
+ lowered << IR::IfStmt.new(condition: tag_check, then_body: [IR::GotoStmt.new(label: goto_label)], else_body: [])
372
+ end
373
+
374
+ if arm_name && !wildcard_arm_pattern?(arm.pattern) && scrutinee_type.has_payload?(arm_name)
375
+ fields = scrutinee_type.arm(arm_name)
376
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
377
+ data_expr = IR::Member.new(receiver: expression, member: "data", type: nil)
378
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
379
+ payload_c_name = fresh_c_temp_name(arm_local_env, "match_payload")
380
+ arm_local_env[:scopes].last["__mt_payload"] = local_binding(type: payload_type, linkage_name: payload_c_name, mutable: true, pointer: false)
381
+
382
+ arm_body_ir = []
383
+ arm_body_ir << IR::LocalDecl.new(name: payload_c_name, linkage_name: payload_c_name, type: payload_type, value: arm_expr)
384
+
385
+ nested_struct_fields = nil
386
+ nested_struct_c_name = nil
387
+ nested_struct_type = nil
388
+ if fields.size == 1 && fields.values.first.is_a?(Types::Struct)
389
+ nested_struct_type = fields.values.first
390
+ nested_struct_fields = nested_struct_type.fields
391
+ single_field_name = fields.keys.first
392
+ nested_struct_c_name = fresh_c_temp_name(arm_local_env, "match_struct")
393
+ struct_expr = IR::Member.new(
394
+ receiver: IR::Name.new(name: payload_c_name, type: payload_type, pointer: false),
395
+ member: single_field_name,
396
+ type: nested_struct_type,
397
+ )
398
+ arm_body_ir << IR::LocalDecl.new(name: nested_struct_c_name, linkage_name: nested_struct_c_name, type: nested_struct_type, value: struct_expr)
399
+ end
400
+
401
+ if arm.pattern.is_a?(AST::Call) && !arm.pattern.arguments.empty?
402
+ pattern_fields = nested_struct_fields || fields
403
+ pattern_receiver_name = nested_struct_c_name || payload_c_name
404
+ pattern_receiver_type = nested_struct_type || payload_type
405
+
406
+ # Phase 1: Emit guard / equality checks (goto next arm on failure)
407
+ arm.pattern.arguments.each do |arg|
408
+ # Guard: hp > 0
409
+ if !arg.name && arg.value.is_a?(AST::BinaryOp) && arg.value.left.is_a?(AST::Identifier)
410
+ field_name = arg.value.left.name
411
+ next unless pattern_fields.key?(field_name)
412
+
413
+ field_type = pattern_fields[field_name]
414
+ field_expr = IR::Member.new(receiver: IR::Name.new(name: pattern_receiver_name, type: pattern_receiver_type, pointer: false), member: field_name, type: field_type)
415
+ field_expr = dereference_if_self_ref(field_expr, field_type, scrutinee_type)
416
+ rhs_expr = lower_expression(arg.value.right, env: arm_local_env, expected_type: field_type)
417
+ guard_condition = IR::Binary.new(operator: arg.value.operator, left: field_expr, right: rhs_expr, type: @ctx.types.fetch("bool"))
418
+ arm_body_ir << IR::IfStmt.new(condition: guard_condition, then_body: [], else_body: [IR::GotoStmt.new(label: goto_label)])
419
+ end
420
+
421
+ # Equality: kind = Kind.boss
422
+ if arg.name
423
+ field_name = arg.name
424
+ next unless pattern_fields.key?(field_name)
425
+
426
+ field_type = pattern_fields[field_name]
427
+ field_expr = IR::Member.new(receiver: IR::Name.new(name: pattern_receiver_name, type: pattern_receiver_type, pointer: false), member: field_name, type: field_type)
428
+ field_expr = dereference_if_self_ref(field_expr, field_type, scrutinee_type)
429
+ value_expr = lower_expression(arg.value, env: arm_local_env, expected_type: field_type)
430
+ eq_check = IR::Binary.new(operator: "!=", left: field_expr, right: value_expr, type: @ctx.types.fetch("bool"))
431
+ arm_body_ir << IR::IfStmt.new(condition: eq_check, then_body: [IR::GotoStmt.new(label: goto_label)], else_body: [])
432
+ end
433
+ end
434
+
435
+ # Phase 2: Emit bindings (bare identifiers, excluding _ discard)
436
+ arm.pattern.arguments.each do |arg|
437
+ next if arg.name
438
+ next unless arg.value.is_a?(AST::Identifier)
439
+ next if arg.value.name == "_"
440
+
441
+ field_name = arg.value.name
442
+ next unless pattern_fields.key?(field_name)
443
+
444
+ field_type = pattern_fields[field_name]
445
+ binding_c = c_local_name(field_name)
446
+ field_expr = IR::Member.new(receiver: IR::Name.new(name: pattern_receiver_name, type: pattern_receiver_type, pointer: false), member: field_name, type: field_type)
447
+ field_expr = dereference_if_self_ref(field_expr, field_type, scrutinee_type)
448
+ arm_body_ir << IR::LocalDecl.new(name: binding_c, linkage_name: binding_c, type: field_type, value: field_expr)
449
+ arm_local_env[:scopes].last[field_name] = local_binding(type: field_type, linkage_name: binding_c, mutable: false, pointer: false)
450
+ end
451
+ end
452
+
453
+ # Handle as-binding
454
+ if arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
455
+ binding_c = c_local_name(arm.binding_name)
456
+ if arm_name && scrutinee_type.has_payload?(arm_name)
457
+ payload_key = "__mt_payload"
458
+ if arm_local_env[:scopes].last.key?(payload_key)
459
+ payload_binding = arm_local_env[:scopes].last[payload_key]
460
+ arm_local_env[:scopes].last[arm.binding_name] = local_binding(type: payload_binding[:type], linkage_name: binding_c, mutable: true, pointer: false)
461
+ payload_ref = IR::Name.new(name: payload_binding[:linkage_name], type: payload_binding[:type], pointer: false)
462
+ arm_body_ir << IR::LocalDecl.new(name: arm.binding_name, linkage_name: binding_c, type: payload_binding[:type], value: payload_ref)
463
+ end
464
+ end
465
+ end
466
+
467
+ body = lower_block(
468
+ arm.body,
469
+ env: arm_local_env,
470
+ active_defers: active_defers + local_defers,
471
+ return_type:,
472
+ loop_flow: arm_loop_flow,
473
+ allow_return:,
474
+ )
475
+ arm_body_ir.concat(body)
476
+ arm_body_ir << IR::GotoStmt.new(label: match_end_label)
477
+
478
+ lowered << IR::BlockStmt.new(body: arm_body_ir)
479
+ else
480
+ body = lower_block(
481
+ arm.body,
482
+ env: arm_local_env,
483
+ active_defers: active_defers + local_defers,
484
+ return_type:,
485
+ loop_flow: arm_loop_flow,
486
+ allow_return:,
487
+ )
488
+ lowered << IR::BlockStmt.new(body: body + [IR::GotoStmt.new(label: match_end_label)])
489
+ end
490
+ end
491
+
492
+ lowered << IR::LabelStmt.new(name: arm_next_label)
493
+ lowered << IR::LabelStmt.new(name: match_end_label)
494
+ else
495
+ kind_type = @ctx.types.fetch("int")
496
+ kind_expr = IR::Member.new(receiver: expression, member: "kind", type: kind_type)
497
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
498
+ cases = statement.arms.map do |arm|
499
+ arm_local_env = duplicate_env(local_env)
500
+ binding_decl = if arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
501
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
502
+ if arm_name && scrutinee_type.has_payload?(arm_name)
503
+ fields = scrutinee_type.arm(arm_name)
504
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
505
+ data_expr = IR::Member.new(receiver: expression, member: "data", type: nil)
506
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
507
+ binding_c = c_local_name(arm.binding_name)
508
+ arm_local_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: true, pointer: false)
509
+ IR::LocalDecl.new(name: arm.binding_name, linkage_name: binding_c, type: payload_type, value: arm_expr)
510
+ end
511
+ end
512
+ body = lower_block(
513
+ arm.body,
514
+ env: arm_local_env,
515
+ active_defers: active_defers + local_defers,
516
+ return_type:,
517
+ loop_flow: arm_loop_flow,
518
+ allow_return:,
519
+ )
520
+ body = [binding_decl, *body].compact if binding_decl
521
+ if wildcard_arm_pattern?(arm.pattern)
522
+ IR::SwitchDefaultCase.new(body:)
523
+ else
524
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
525
+ IR::SwitchCase.new(value: IR::Name.new(name: enum_member_c_name(scrutinee_type, "kind_#{arm_name}"), type: kind_type, pointer: false), body:)
526
+ end
527
+ end
528
+ lowered << IR::SwitchStmt.new(expression: kind_expr, cases:, exhaustive: true)
529
+ end # inner if/else for struct patterns
530
+ elsif scrutinee_type.is_a?(Types::StringView)
531
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
532
+ bool_type = @ctx.types.fetch("bool")
533
+ non_wildcard = statement.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
534
+ wildcard = statement.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
535
+ else_body = if wildcard
536
+ lower_block(
537
+ wildcard.body,
538
+ env: local_env,
539
+ active_defers: active_defers + local_defers,
540
+ return_type:,
541
+ loop_flow: arm_loop_flow,
542
+ allow_return:,
543
+ )
544
+ else
545
+ []
546
+ end
547
+ non_wildcard.reverse_each do |arm|
548
+ arm_body = lower_block(
549
+ arm.body,
550
+ env: local_env,
551
+ active_defers: active_defers + local_defers,
552
+ return_type:,
553
+ loop_flow: arm_loop_flow,
554
+ allow_return:,
555
+ )
556
+ lit = lower_expression(arm.pattern, env: local_env, expected_type: scrutinee_type)
557
+ cond = IR::Binary.new(operator: "==", left: expression, right: lit, type: bool_type)
558
+ else_body = [IR::IfStmt.new(condition: cond, then_body: arm_body, else_body:)]
559
+ end
560
+ lowered.concat(else_body)
561
+ elsif scrutinee_type.is_a?(Types::Tuple)
562
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
563
+ bool_type = @ctx.types.fetch("bool")
564
+ non_wildcard = statement.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
565
+ wildcard = statement.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
566
+ else_body = if wildcard
567
+ lower_block(
568
+ wildcard.body,
569
+ env: local_env,
570
+ active_defers: active_defers + local_defers,
571
+ return_type:,
572
+ loop_flow: arm_loop_flow,
573
+ allow_return:,
574
+ )
575
+ else
576
+ []
577
+ end
578
+ non_wildcard.reverse_each do |arm|
579
+ arm_body = lower_block(
580
+ arm.body,
581
+ env: local_env,
582
+ active_defers: active_defers + local_defers,
583
+ return_type:,
584
+ loop_flow: arm_loop_flow,
585
+ allow_return:,
586
+ )
587
+ cond = tuple_arm_condition(arm, expression, scrutinee_type, bool_type, env: local_env)
588
+ else_body = [IR::IfStmt.new(condition: cond, then_body: arm_body, else_body:)]
589
+ end
590
+ lowered.concat(else_body)
591
+ else
592
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
593
+ cases = statement.arms.map do |arm|
594
+ body = lower_block(
595
+ arm.body,
596
+ env: local_env,
597
+ active_defers: active_defers + local_defers,
598
+ return_type:,
599
+ loop_flow: arm_loop_flow,
600
+ allow_return:,
601
+ )
602
+ if wildcard_arm_pattern?(arm.pattern)
603
+ IR::SwitchDefaultCase.new(body:)
604
+ else
605
+ value = lower_expression(arm.pattern, env: local_env, expected_type: scrutinee_type)
606
+ IR::SwitchCase.new(value:, body:)
607
+ end
608
+ end
609
+ lowered << IR::SwitchStmt.new(expression:, cases:, exhaustive: true)
610
+ end
611
+ lowered.concat(expression_cleanups.flat_map(&:itself))
612
+ end
613
+ when AST::StaticAssert
614
+ lowered << lower_static_assert(statement, env: local_env)
615
+ when AST::ForStmt
616
+ if statement.inline
617
+ lowered.concat(lower_inline_for_stmt(statement, env: local_env, active_defers:, return_type:, allow_return:))
618
+ else
619
+ lowered << lower_for_stmt(statement, env: local_env, active_defers: active_defers + local_defers, return_type:, allow_return:)
620
+ end
621
+ when AST::ParallelBlockStmt
622
+ lowered << lower_parallel_block_stmt(statement, env: local_env, active_defers: active_defers + local_defers)
623
+ when AST::GatherStmt
624
+ lowered << lower_gather_stmt(statement, env: local_env)
625
+ when AST::WhileStmt
626
+ if statement.inline
627
+ lowered.concat(lower_inline_while_stmt(statement, env: local_env, active_defers:, return_type:, allow_return:))
628
+ else
629
+ lowered << lower_while_stmt(statement, env: local_env, active_defers: active_defers + local_defers, return_type:, allow_return:)
630
+ end
631
+ when AST::PassStmt
632
+ nil
633
+ when AST::BreakStmt
634
+ raise LoweringError, "break must be inside a loop" unless loop_flow
635
+
636
+ lowered.concat(lower_loop_exit(loop_flow[:break_target], local_defers, loop_flow[:break_defers]))
637
+ when AST::ContinueStmt
638
+ raise LoweringError, "continue must be inside a loop" unless loop_flow
639
+
640
+ lowered.concat(lower_loop_exit(loop_flow[:continue_target], local_defers, loop_flow[:continue_defers]))
641
+ when AST::ReturnStmt
642
+ raise LoweringError, "return is not allowed inside defer blocks" unless allow_return
643
+
644
+ value = nil
645
+ prepared_setup = []
646
+ prepared_value = statement.value
647
+ prepared_cleanups = []
648
+ if statement.value
649
+ local_env[:current_local_name] = linkage_name
650
+ prepared_setup, prepared_value, prepared_cleanups = prepare_expression_with_cleanups(
651
+ statement.value,
652
+ env: local_env,
653
+ expected_type: return_type,
654
+ allow_root_statement_foreign: true,
655
+ materialize_array_calls: !array_type?(return_type),
656
+ )
657
+ lowered.concat(prepared_setup)
658
+ end
659
+ if prepared_value && (foreign_call = foreign_call_info(prepared_value, local_env))
660
+ setup, value = lower_foreign_call_statement(foreign_call, env: local_env, expected_type: return_type, statement_position: false)
661
+ lowered.concat(setup)
662
+ end
663
+ value ||= prepared_value ? lower_contextual_expression(
664
+ prepared_value,
665
+ env: local_env,
666
+ expected_type: return_type,
667
+ contextual_int_to_float: contextual_int_to_float_target?(return_type),
668
+ ) : nil
669
+ if prepared_cleanups.any? && cstr_trackable_type?(return_type)
670
+ raise LoweringError, "formatted string temporaries cannot be returned as borrowed text; use std.fmt.format(f\"...\") when ownership must escape"
671
+ end
672
+
673
+ prepared_cleanup_list = prepared_cleanups.flat_map(&:itself)
674
+ if prepared_cleanup_list.any? && return_type.is_a?(Types::Struct) && struct_contains_string_field?(return_type)
675
+ prepared_cleanup_list = prepared_cleanup_list.reject { |stmt| stmt.is_a?(IR::ExpressionStmt) && stmt.expression.is_a?(IR::Call) && stmt.expression.callee == "mt_format_str_release" }
676
+ end
677
+ cleanup = prepared_cleanup_list + cleanup_statements(local_defers, active_defers)
678
+ needs_proc_retain = value && contains_proc_storage_type?(return_type) && !local_defers.empty? && !expression_contains_proc_expr?(prepared_value)
679
+ if value && (!cleanup.empty? && !cleanup_safe_return_expression?(prepared_value) || needs_proc_retain)
680
+ return_value_name = fresh_c_temp_name(local_env, "return_value")
681
+ lowered << IR::LocalDecl.new(name: return_value_name, linkage_name: return_value_name, type: return_type, value:)
682
+ value = IR::Name.new(name: return_value_name, type: return_type, pointer: false)
683
+ end
684
+ lowered.concat(lower_proc_contained_retain_statements(value, return_type)) if needs_proc_retain
685
+ lowered.concat(cleanup)
686
+ lowered << IR::ReturnStmt.new(value:, line: statement.line, source_path: @ctx.current_analysis_path)
687
+ when AST::ExpressionStmt
688
+ if (format_sink_statements = lower_explicit_format_sink_expression_statement(statement.expression, env: local_env, line: statement.line))
689
+ lowered.concat(format_sink_statements)
690
+ next
691
+ end
692
+
693
+ expression_expected_type = if statement.expression.is_a?(AST::UnaryOp) && statement.expression.operator == "?"
694
+ nil
695
+ else
696
+ infer_expression_type(statement.expression, env: local_env)
697
+ end
698
+ prepared_setup, prepared_expression, prepared_cleanups = prepare_expression_with_cleanups(
699
+ statement.expression,
700
+ env: local_env,
701
+ expected_type: expression_expected_type,
702
+ allow_root_statement_foreign: true,
703
+ allow_void_propagation: true,
704
+ )
705
+ lowered.concat(prepared_setup)
706
+ if prepared_expression && (foreign_call = foreign_call_info(prepared_expression, local_env))
707
+ setup, value = lower_foreign_call_statement(
708
+ foreign_call,
709
+ env: local_env,
710
+ expected_type: foreign_call[:binding].type.return_type,
711
+ statement_position: true,
712
+ discard_result: true,
713
+ )
714
+ lowered.concat(setup)
715
+ lowered.concat(prepared_cleanups.flat_map(&:itself))
716
+ local_env[:scopes] = scopes_with_refinements(local_env[:scopes], consuming_foreign_call_refinements(foreign_call, local_env))
717
+ elsif prepared_expression
718
+ lowered << IR::ExpressionStmt.new(expression: lower_expression(prepared_expression, env: local_env), line: statement.line, source_path: @ctx.current_analysis_path)
719
+ lowered.concat(prepared_cleanups.flat_map(&:itself))
720
+ else
721
+ lowered.concat(prepared_cleanups.flat_map(&:itself))
722
+ end
723
+ when AST::EmitStmt
724
+ lowered.concat(lower_emit_stmt(statement, env: local_env))
725
+ when AST::WhenStmt
726
+ discriminant = compile_time_const_value(statement.discriminant)
727
+ chosen_branch = statement.branches.find do |branch|
728
+ discriminant == compile_time_const_value(branch.pattern)
729
+ end
730
+
731
+ if chosen_branch
732
+ lowered.concat(lower_block(
733
+ chosen_branch.body,
734
+ env: local_env,
735
+ active_defers:,
736
+ return_type:,
737
+ loop_flow:,
738
+ allow_return:,
739
+ ))
740
+ elsif statement.else_body
741
+ lowered.concat(lower_block(
742
+ statement.else_body,
743
+ env: local_env,
744
+ active_defers:,
745
+ return_type:,
746
+ loop_flow:,
747
+ allow_return:,
748
+ ))
749
+ end
750
+ else
751
+ raise LoweringError, "unsupported statement #{statement.class.name}"
752
+ end
753
+ end
754
+
755
+ unless terminating_ir_statement?(lowered.last)
756
+ lowered.concat(cleanup_statements(local_defers, []))
757
+ end
758
+ lowered
759
+ end
760
+
761
+ def lower_proc_expression_for_local(expression, env:, local_name:, proc_type:)
762
+ captures = proc_capture_entries(expression, env)
763
+ captures.each do |capture|
764
+ if ref_type?(capture[:type]) || contains_ref_type?(capture[:type])
765
+ raise LoweringError, "proc capture #{capture[:name]} cannot use ref types"
766
+ end
767
+ end
768
+
769
+ proc_id = fresh_proc_symbol
770
+ invoke_c_name = "#{@ctx.module_prefix}__proc_#{proc_id}__invoke"
771
+ release_linkage_name = "#{@ctx.module_prefix}__proc_#{proc_id}__release"
772
+ retain_c_name = "#{@ctx.module_prefix}__proc_#{proc_id}__retain"
773
+ env_struct_type = nil
774
+ setup = []
775
+
776
+ env_value = if captures.empty?
777
+ IR::NullLiteral.new(type: proc_env_pointer_type)
778
+ else
779
+ env_struct_type = Types::Struct.new("#{@ctx.module_prefix}__proc_#{proc_id}__env").define_fields(
780
+ { "__mt_ref_count" => @ctx.types.fetch("ptr_uint") }.merge(captures.each_with_object({}) { |capture, fields| fields[capture[:field_name]] = capture[:type] }),
781
+ )
782
+ @artifacts.synthetic_structs << IR::StructDecl.new(
783
+ name: env_struct_type.name,
784
+ linkage_name: env_struct_type.name,
785
+ fields: [IR::Field.new(name: "__mt_ref_count", type: @ctx.types.fetch("ptr_uint")), *captures.map { |capture| IR::Field.new(name: capture[:field_name], type: capture[:type]) }],
786
+ packed: false,
787
+ alignment: nil,
788
+ )
789
+
790
+ env_pointer_type = pointer_to(env_struct_type)
791
+ env_name = fresh_c_temp_name(env, "#{local_name}_env")
792
+ raw_allocation = IR::Call.new(
793
+ callee: "mt_async_alloc",
794
+ arguments: [IR::SizeofExpr.new(target_type: env_struct_type, type: @ctx.types.fetch("ptr_uint"))],
795
+ type: proc_env_pointer_type,
796
+ )
797
+ setup << IR::LocalDecl.new(
798
+ name: env_name,
799
+ linkage_name: env_name,
800
+ type: env_pointer_type,
801
+ value: IR::Cast.new(target_type: env_pointer_type, expression: raw_allocation, type: env_pointer_type),
802
+ )
803
+ env_pointer = IR::Name.new(name: env_name, type: env_pointer_type, pointer: false)
804
+ setup << IR::Assignment.new(
805
+ target: IR::Member.new(receiver: env_pointer, member: "__mt_ref_count", type: @ctx.types.fetch("ptr_uint")),
806
+ operator: "=",
807
+ value: IR::IntegerLiteral.new(value: 1, type: @ctx.types.fetch("ptr_uint")),
808
+ )
809
+ captures.each do |capture|
810
+ setup << IR::Assignment.new(
811
+ target: IR::Member.new(receiver: env_pointer, member: capture[:field_name], type: capture[:type]),
812
+ operator: "=",
813
+ value: lower_expression(AST::Identifier.new(name: capture[:name]), env:, expected_type: capture[:type]),
814
+ )
815
+ end
816
+ captures.each do |capture|
817
+ next unless contains_proc_storage_type?(capture[:type])
818
+
819
+ member = IR::Member.new(receiver: env_pointer, member: capture[:field_name], type: capture[:type])
820
+ setup.concat(lower_proc_contained_retain_statements(member, capture[:type]))
821
+ end
822
+ IR::Cast.new(target_type: proc_env_pointer_type, expression: env_pointer, type: proc_env_pointer_type)
823
+ end
824
+
825
+ @artifacts.synthetic_functions << build_proc_invoke_function(expression, proc_type, captures, env_struct_type, invoke_c_name)
826
+ @artifacts.synthetic_functions << build_proc_release_function(release_linkage_name, env_struct_type)
827
+ @artifacts.synthetic_functions << build_proc_retain_function(retain_c_name, env_struct_type)
828
+
829
+ [
830
+ setup,
831
+ IR::AggregateLiteral.new(
832
+ type: proc_type,
833
+ fields: [
834
+ IR::AggregateField.new(name: "env", value: env_value),
835
+ IR::AggregateField.new(name: "invoke", value: IR::Name.new(name: invoke_c_name, type: proc_invoke_function_type(proc_type), pointer: false)),
836
+ IR::AggregateField.new(name: "release", value: IR::Name.new(name: release_linkage_name, type: proc_release_function_type, pointer: false)),
837
+ IR::AggregateField.new(name: "retain", value: IR::Name.new(name: retain_c_name, type: proc_retain_function_type, pointer: false)),
838
+ ],
839
+ ),
840
+ ]
841
+ end
842
+
843
+ def lower_inline_for_stmt(statement, env:, active_defers:, return_type:, allow_return:)
844
+ iterable = compile_time_const_value(statement.iterables.first, env:)
845
+ return [] unless iterable.is_a?(Array) && !iterable.empty?
846
+
847
+ loop_var_name = statement.bindings.first.name
848
+ loop_var_type = inline_loop_element_type(iterable.first)
849
+ lowered = []
850
+
851
+ iterable.each do |element|
852
+ iter_env = duplicate_env(env)
853
+ current_actual_scope(iter_env[:scopes])[loop_var_name] = local_binding(
854
+ type: loop_var_type,
855
+ linkage_name: c_local_name(loop_var_name),
856
+ mutable: false,
857
+ pointer: false,
858
+ const_value: element,
859
+ )
860
+ emit_stmts, other_stmts = statement.body.partition { |s| s.is_a?(AST::EmitStmt) }
861
+ emit_stmts.each do |emit_stmt|
862
+ lowered.concat(lower_emit_stmt(emit_stmt, env: iter_env))
863
+ rescue LoweringError
864
+ # emit for compile-time-only constructs — skip
865
+ end
866
+ unless other_stmts.empty?
867
+ saved_type_env = @type_resolution_env
868
+ saved_bypass = @bypass_sema_type_cache
869
+ # Each unrolled iteration may bind the loop var to a different value
870
+ # (e.g. a field whose `.type` differs per field), so expression types
871
+ # must be re-inferred per iteration instead of reusing the single
872
+ # sema-cached type computed for the representative first element.
873
+ @type_resolution_env = iter_env
874
+ @bypass_sema_type_cache = true
875
+ begin
876
+ body = lower_block(other_stmts, env: iter_env, active_defers:, return_type:, loop_flow: nil, allow_return:)
877
+ ensure
878
+ @type_resolution_env = saved_type_env
879
+ @bypass_sema_type_cache = saved_bypass
880
+ end
881
+ lowered << IR::BlockStmt.new(body:) unless body.empty?
882
+ end
883
+ end
884
+
885
+ lowered
886
+ end
887
+
888
+ def lower_inline_while_stmt(statement, env:, active_defers:, return_type:, allow_return:)
889
+ condition = compile_time_const_value(statement.condition, env:)
890
+ return [] unless condition
891
+
892
+ iterations = 0
893
+ max_iterations = 10_000
894
+ lowered = []
895
+
896
+ while compile_time_const_value(statement.condition, env:) && iterations < max_iterations
897
+ body = lower_block(statement.body, env:, active_defers:, return_type:, loop_flow: nil, allow_return:)
898
+ lowered << IR::BlockStmt.new(body:) unless body.empty?
899
+ iterations += 1
900
+ end
901
+
902
+ lowered
903
+ end
904
+
905
+ def lower_inline_if_stmt(statement, env:, active_defers:, return_type:, allow_return:)
906
+ chosen_branch = statement.branches.find do |branch|
907
+ ct_value = compile_time_const_value(branch.condition, env:)
908
+ ct_value == true
909
+ end
910
+
911
+ if chosen_branch
912
+ lower_block(chosen_branch.body, env:, active_defers:, return_type:, loop_flow: nil, allow_return:)
913
+ elsif statement.else_body
914
+ lower_block(statement.else_body, env:, active_defers:, return_type:, loop_flow: nil, allow_return:)
915
+ else
916
+ []
917
+ end
918
+ end
919
+
920
+ def lower_inline_match_stmt(statement, env:, active_defers:, return_type:, allow_return:)
921
+ scrutinee = compile_time_const_value(statement.expression, env:)
922
+ return [] unless scrutinee
923
+
924
+ chosen_arm = statement.arms.find do |arm|
925
+ scrutinee == compile_time_const_value(arm.pattern, env:)
926
+ end
927
+
928
+ return [] unless chosen_arm
929
+
930
+ lower_block(chosen_arm.body, env:, active_defers:, return_type:, loop_flow: nil, allow_return:)
931
+ end
932
+
933
+ def inline_loop_element_type(element)
934
+ if element.is_a?(Types::FieldHandle)
935
+ @ctx.types.fetch("field_handle")
936
+ elsif element.is_a?(Types::CallableHandle)
937
+ @ctx.types.fetch("callable_handle")
938
+ elsif element.is_a?(Types::AttributeHandle)
939
+ @ctx.types.fetch("attribute_handle")
940
+ elsif element.is_a?(Types::MemberHandle)
941
+ @ctx.types.fetch("member_handle")
942
+ elsif element.is_a?(Types::StructHandle)
943
+ @ctx.types.fetch("struct_handle")
944
+ else
945
+ @ctx.types.fetch("int")
946
+ end
947
+ end
948
+
949
+ def lower_emit_stmt(statement, env:)
950
+ decl = statement.declaration
951
+ case decl
952
+ when AST::FunctionDef
953
+ lower_emitted_function(decl, env:)
954
+ when AST::StructDecl
955
+ lower_emitted_struct(decl)
956
+ when AST::ConstDecl
957
+ lower_emitted_const(decl, env:)
958
+ else
959
+ raise LoweringError, "emit is not supported for #{decl.class.name}"
960
+ end
961
+ end
962
+
963
+ def lower_emitted_function(decl, env:)
964
+ params = []
965
+ param_setup = []
966
+ return_type = decl.return_type ? resolve_type_ref(decl.return_type) : @ctx.types.fetch("void")
967
+ linkage_name = "#{@ctx.module_prefix}#{decl.name}"
968
+
969
+ decl.params.each do |param|
970
+ param_type = param.type ? resolve_type_ref(param.type) : @ctx.types.fetch("int")
971
+ param_linkage_name = c_local_name(param.name)
972
+ params << IR::Param.new(name: param.name, linkage_name: param_linkage_name, type: param_type, pointer: false)
973
+ env[:scopes].last[param.name] = local_binding(type: param_type, linkage_name: param_linkage_name, mutable: false, pointer: false)
974
+ end
975
+
976
+ body = lower_block(decl.body, env:, active_defers: [], return_type:, loop_flow: nil, allow_return: true)
977
+ body = param_setup + body
978
+ func = IR::Function.new(name: decl.name, linkage_name:, params:, return_type:, body:, entry_point: false, method_receiver_param: false)
979
+ @artifacts.emitted_declarations << func
980
+ []
981
+ end
982
+
983
+ def lower_emitted_struct(decl)
984
+ fields = decl.fields.map do |field|
985
+ field_type = field.type ? resolve_type_ref(field.type) : @ctx.types.fetch("int")
986
+ IR::Field.new(name: field.name, type: field_type)
987
+ end
988
+ linkage_name = "#{@ctx.module_prefix}#{decl.name}"
989
+ struct_decl = IR::StructDecl.new(name: decl.name, linkage_name:, fields:, packed: decl.respond_to?(:packed) ? decl.packed : false, alignment: nil)
990
+ @artifacts.emitted_declarations << struct_decl
991
+ []
992
+ end
993
+
994
+ def lower_destructure_decl(statement, env:, lowered:, local_defers:, active_defers:, return_type:, loop_flow:, allow_return:)
995
+ value_type = infer_expression_type(statement.value, env:)
996
+ setup, prepared_value, _cleanups = prepare_expression_with_cleanups(statement.value, env:, expected_type: value_type)
997
+ lowered.concat(setup)
998
+ value = lower_contextual_expression(prepared_value, env:, expected_type: value_type)
999
+
1000
+ temp_name = fresh_c_temp_name(env, "destructure_val")
1001
+ lowered << IR::LocalDecl.new(name: temp_name, linkage_name: temp_name, type: value_type, value:)
1002
+
1003
+ if statement.destructure_type_name
1004
+ # Struct destructure: find fields by name in the struct type
1005
+ destructure_type = value_type
1006
+ fields = destructure_type.fields
1007
+ statement.destructure_bindings.each do |name|
1008
+ next if name == "_"
1009
+ field_type = fields[name]
1010
+ field_expr = IR::Member.new(receiver: IR::Name.new(name: temp_name, type: destructure_type, pointer: false), member: name, type: field_type)
1011
+ decl_c_name = c_local_name(name)
1012
+ lowered << IR::LocalDecl.new(name: decl_c_name, linkage_name: decl_c_name, type: field_type, value: field_expr)
1013
+ env[:scopes].last[name] = local_binding(type: field_type, linkage_name: decl_c_name, mutable: false, pointer: false)
1014
+ end
1015
+ else
1016
+ # Tuple destructure: index by position
1017
+ statement.destructure_bindings.each_with_index do |name, index|
1018
+ next if name == "_"
1019
+ field_name = value_type.field_names[index]
1020
+ field_type = value_type.element_types[index]
1021
+ field_expr = IR::Member.new(receiver: IR::Name.new(name: temp_name, type: value_type, pointer: false), member: field_name, type: field_type)
1022
+ decl_c_name = c_local_name(name)
1023
+ lowered << IR::LocalDecl.new(name: decl_c_name, linkage_name: decl_c_name, type: field_type, value: field_expr)
1024
+ env[:scopes].last[name] = local_binding(type: field_type, linkage_name: decl_c_name, mutable: false, pointer: false)
1025
+ end
1026
+ end
1027
+ end
1028
+
1029
+ def lower_emitted_const(decl, env:)
1030
+ value = lower_static_storage_initializer(decl.value, env:, expected_type: decl.type ? resolve_type_ref(decl.type) : nil)
1031
+ type = decl.type ? resolve_type_ref(decl.type) : infer_expression_type(decl.value, env:)
1032
+ linkage_name = value_c_name(decl.name)
1033
+ constant = IR::Constant.new(name: decl.name, linkage_name:, type:, value:)
1034
+ @artifacts.emitted_declarations << constant
1035
+ []
1036
+ end
1037
+
1038
+ def variant_self_referencing_field?(field_type, scrutinee_type)
1039
+ return false unless field_type.is_a?(Types::Variant) || field_type.is_a?(Types::VariantInstance)
1040
+ return false unless scrutinee_type.is_a?(Types::Variant) || scrutinee_type.is_a?(Types::VariantInstance)
1041
+ field_type == scrutinee_type
1042
+ end
1043
+
1044
+ def dereference_if_self_ref(field_expr, field_type, scrutinee_type)
1045
+ if variant_self_referencing_field?(field_type, scrutinee_type)
1046
+ IR::Unary.new(operator: "*", operand: field_expr, type: field_type)
1047
+ else
1048
+ field_expr
1049
+ end
1050
+ end
1051
+ end
1052
+ end