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,1308 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class SemanticAnalyzer
5
+ class Checker
6
+ private
7
+
8
+ def check_block(statements, scopes:, return_type:, allow_return: true)
9
+ statements ||= []
10
+ with_return_context(return_type, allow_return:) do
11
+ with_nested_scope(scopes) do |nested_scopes|
12
+ with_type_resolution_scopes(nested_scopes) do
13
+ statements.each_with_index do |statement, idx|
14
+ begin
15
+ record_local_completion_snapshot(
16
+ statement.respond_to?(:line) ? statement.line : nil,
17
+ statement.respond_to?(:column) ? statement.column : 0,
18
+ nested_scopes,
19
+ )
20
+ refinements = check_statement(statement, scopes: nested_scopes, return_type:, allow_return:)
21
+ apply_continuation_refinements!(nested_scopes, refinements)
22
+ if @nullability_flow_result && idx + 1 < statements.length
23
+ apply_nullability_continuation_refinements!(nested_scopes, statements[idx + 1])
24
+ end
25
+ record_local_completion_snapshot(statement_end_line(statement), 1_000_000, nested_scopes)
26
+ rescue SemanticError => e
27
+ if @collecting_errors
28
+ @structural_errors << e
29
+ next
30
+ end
31
+
32
+ raise e unless e.line.nil?
33
+
34
+ stmt_line = statement.respond_to?(:line) ? statement.line : nil
35
+ raise e if stmt_line.nil?
36
+
37
+ raise_sema_error(e.message, statement)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def cfg_block_always_terminates?(statements)
46
+ ControlFlow::Termination.block_always_terminates?(statements, ignore_name: ->(_name) { false })
47
+ end
48
+
49
+ def check_statement(statement, scopes:, return_type:, allow_return: true)
50
+ with_error_node(statement) do
51
+ case statement
52
+ when AST::ErrorBlockStmt
53
+ if statement.header_type == :unsafe
54
+ @unsafe_statement_lines << statement.line
55
+ begin
56
+ with_unsafe do
57
+ check_block(statement.body, scopes:, return_type:, allow_return:)
58
+ end
59
+ ensure
60
+ @unsafe_statement_lines.pop
61
+ end
62
+ elsif statement.header_type == :if && statement.header_expression
63
+ validate_consuming_foreign_expression!(statement.header_expression, scopes:, root_allowed: false)
64
+ condition_type = infer_expression(statement.header_expression, scopes:, expected_type: @ctx.types.fetch("bool"))
65
+ ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "if condition must be bool, got #{condition_type}", expression: statement.header_expression, line: statement.line, column: statement.column)
66
+ true_refinements = flow_refinements(statement.header_expression, truthy: true, scopes:)
67
+ check_block(statement.body, scopes: scopes_with_refinements(scopes, true_refinements), return_type:, allow_return:)
68
+ return flow_refinements(statement.header_expression, truthy: false, scopes:) if cfg_block_always_terminates?(statement.body)
69
+ elsif statement.header_type == :while && statement.header_expression
70
+ validate_consuming_foreign_expression!(statement.header_expression, scopes:, root_allowed: false)
71
+ condition_type = infer_expression(statement.header_expression, scopes:, expected_type: @ctx.types.fetch("bool"))
72
+ ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "while condition must be bool, got #{condition_type}", expression: statement.header_expression, line: statement.line, column: statement.column)
73
+ with_loop do
74
+ body_scopes = scopes_with_refinements(scopes, flow_refinements(statement.header_expression, truthy: true, scopes:))
75
+ check_block(statement.body, scopes: body_scopes, return_type:, allow_return:)
76
+ end
77
+ elsif statement.header_type == :for
78
+ if statement.header_bindings && statement.header_iterables
79
+ check_for_stmt(recovered_for_statement(statement), scopes:, return_type:, allow_return:)
80
+ else
81
+ with_loop do
82
+ check_block(statement.body, scopes:, return_type:, allow_return:)
83
+ end
84
+ end
85
+ else
86
+ check_block(statement.body, scopes:, return_type:, allow_return:)
87
+ end
88
+ when AST::LocalDecl
89
+ check_local_decl(statement, scopes:, return_type:, allow_return:)
90
+ when AST::ErrorStmt
91
+ nil
92
+ when AST::Assignment
93
+ check_assignment(statement, scopes:)
94
+ when AST::IfStmt
95
+ if statement.inline
96
+ check_inline_if_stmt(statement, scopes:, return_type:, allow_return:)
97
+ next
98
+ end
99
+
100
+ false_refinements = {}
101
+ branch_bodies_terminate = []
102
+ statement.branches.each do |branch|
103
+ branch_scopes = scopes_with_refinements(scopes, false_refinements)
104
+ if branch.condition.is_a?(AST::ErrorExpr)
105
+ check_block(branch.body, scopes: branch_scopes, return_type:, allow_return:)
106
+ branch_bodies_terminate << cfg_block_always_terminates?(branch.body)
107
+ next
108
+ end
109
+
110
+ validate_consuming_foreign_expression!(branch.condition, scopes: branch_scopes, root_allowed: false)
111
+ condition_type = infer_expression(branch.condition, scopes: branch_scopes, expected_type: @ctx.types.fetch("bool"))
112
+ ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "if condition must be bool, got #{condition_type}", expression: branch.condition, line: branch.line, column: branch.column)
113
+ true_refinements = merge_refinements(false_refinements, flow_refinements(branch.condition, truthy: true, scopes: branch_scopes))
114
+ check_block(branch.body, scopes: scopes_with_refinements(scopes, true_refinements), return_type:, allow_return:)
115
+ branch_bodies_terminate << cfg_block_always_terminates?(branch.body)
116
+ false_refinements = merge_refinements(false_refinements, flow_refinements(branch.condition, truthy: false, scopes: branch_scopes))
117
+ end
118
+ check_block(statement.else_body, scopes: scopes_with_refinements(scopes, false_refinements), return_type:, allow_return:) if statement.else_body
119
+ return false_refinements if statement.else_body.nil? && branch_bodies_terminate.all?
120
+ when AST::MatchStmt
121
+ if statement.inline
122
+ check_inline_match_stmt(statement, scopes:, return_type:, allow_return:)
123
+ else
124
+ check_match_stmt(statement, scopes:, return_type:, allow_return:)
125
+ end
126
+ when AST::UnsafeStmt
127
+ @unsafe_statement_lines << statement.line
128
+ begin
129
+ with_unsafe do
130
+ check_block(statement.body, scopes:, return_type:, allow_return:)
131
+ end
132
+ ensure
133
+ @unsafe_statement_lines.pop
134
+ end
135
+ when AST::StaticAssert
136
+ check_static_assert(statement, scopes:)
137
+ when AST::EmitStmt
138
+ check_emit_stmt(statement)
139
+ when AST::ForStmt
140
+ if statement.inline
141
+ check_inline_for_stmt(statement, scopes:, return_type:, allow_return:)
142
+ else
143
+ check_for_stmt(statement, scopes:, return_type:, allow_return:)
144
+ end
145
+ when AST::ParallelBlockStmt
146
+ check_parallel_block_stmt(statement, scopes:, return_type:)
147
+ when AST::GatherStmt
148
+ check_gather_stmt(statement, scopes:)
149
+ when AST::WhileStmt
150
+ if statement.inline
151
+ check_inline_while_stmt(statement, scopes:, return_type:, allow_return:)
152
+ elsif statement.condition.is_a?(AST::ErrorExpr)
153
+ with_loop do
154
+ check_block(statement.body, scopes:, return_type:, allow_return:)
155
+ end
156
+ else
157
+ validate_consuming_foreign_expression!(statement.condition, scopes:, root_allowed: false)
158
+ condition_type = infer_expression(statement.condition, scopes:, expected_type: @ctx.types.fetch("bool"))
159
+ ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "while condition must be bool, got #{condition_type}", expression: statement.condition, line: statement.line, column: statement.column)
160
+ with_loop do
161
+ body_scopes = scopes_with_refinements(scopes, flow_refinements(statement.condition, truthy: true, scopes:))
162
+ check_block(statement.body, scopes: body_scopes, return_type:, allow_return:)
163
+ end
164
+ end
165
+ when AST::PassStmt
166
+ nil
167
+ when AST::BreakStmt
168
+ raise_sema_error("break must be inside a loop") unless inside_loop?
169
+ when AST::ContinueStmt
170
+ raise_sema_error("continue must be inside a loop") unless inside_loop?
171
+ when AST::ReturnStmt
172
+ raise_sema_error("return is not allowed inside defer blocks") unless allow_return
173
+
174
+ validate_consuming_foreign_expression!(statement.value, scopes:, root_allowed: false) if statement.value
175
+ value_type = statement.value ? infer_expression(statement.value, scopes:, expected_type: return_type) : @ctx.types.fetch("void")
176
+ ensure_assignable!(
177
+ value_type,
178
+ return_type,
179
+ "return type mismatch: expected #{return_type}, got #{value_type}",
180
+ expression: statement.value,
181
+ contextual_int_to_float: contextual_int_to_float_target?(return_type),
182
+ line: statement.line,
183
+ )
184
+ when AST::DeferStmt
185
+ if statement.body
186
+ with_loop_barrier do
187
+ check_block(statement.body, scopes:, return_type:, allow_return: false)
188
+ end
189
+ else
190
+ validate_consuming_foreign_expression!(statement.expression, scopes:, root_allowed: true)
191
+ validate_hoistable_foreign_expression!(statement.expression, scopes:, root_hoistable: false)
192
+ infer_expression(statement.expression, scopes:)
193
+ end
194
+ when AST::ExpressionStmt
195
+ validate_consuming_foreign_expression!(statement.expression, scopes:, root_allowed: true)
196
+ if statement.expression.is_a?(AST::UnaryOp) && statement.expression.operator == "?"
197
+ infer_propagate_expression(statement.expression.operand, scopes:, allow_void_success: true)
198
+ else
199
+ infer_expression(statement.expression, scopes:)
200
+ end
201
+ return consuming_foreign_call_refinements(statement.expression, scopes:)
202
+ when AST::WhenStmt
203
+ check_when_stmt(statement, scopes:, return_type:, allow_return:)
204
+ else
205
+ raise_sema_error("unsupported statement #{statement.class.name}")
206
+ end
207
+
208
+ nil
209
+ end
210
+ end
211
+
212
+ def check_local_decl(statement, scopes:, return_type:, allow_return:)
213
+ if statement.destructure_bindings
214
+ check_local_decl_destructure(statement, scopes:, return_type:, allow_return:)
215
+ return
216
+ end
217
+
218
+ current_scope = current_actual_scope(scopes)
219
+ discard_binding = statement.name == "_" || let_else_discard_binding_syntax?(statement)
220
+ raise_sema_error("duplicate local #{statement.name}") if !discard_binding && current_scope.key?(statement.name)
221
+ ensure_non_reserved_primitive_name!(statement.name, kind_label: "local", line: statement.line, column: statement.column) unless discard_binding
222
+
223
+ declared_type = statement.type ? resolve_type_ref(statement.type) : nil
224
+ if statement.value
225
+ validate_consuming_foreign_expression!(statement.value, scopes:, root_allowed: false)
226
+ inferred_type = infer_expression(statement.value, scopes:, expected_type: declared_type)
227
+ else
228
+ raise_sema_error("local #{statement.name} without initializer requires an explicit type") unless declared_type
229
+
230
+ begin
231
+ zero_initializable_type?(declared_type)
232
+ rescue SemanticError
233
+ raise_sema_error("local #{statement.name} without initializer requires a zero-initializable type, got #{declared_type}")
234
+ end
235
+
236
+ inferred_type = declared_type
237
+ end
238
+
239
+ storage_type, final_type, const_value =
240
+ if statement.else_body || statement.recovered_else
241
+ check_local_decl_let_else(statement, scopes:, return_type:, allow_return:,
242
+ discard_binding:, declared_type:, inferred_type:)
243
+ else
244
+ check_local_decl_plain(statement, scopes:, discard_binding:,
245
+ declared_type:, inferred_type:)
246
+ end
247
+
248
+ if noncopyable_event_storage_type?(final_type) && !fresh_noncopyable_event_initializer?(statement.value, final_type, scopes:)
249
+ raise_sema_error("local #{statement.name} cannot copy event storage type #{final_type}")
250
+ end
251
+
252
+ unless discard_binding
253
+ current_scope[statement.name] = value_binding(
254
+ name: statement.name,
255
+ type: storage_type,
256
+ mutable: statement.kind == :var,
257
+ kind: statement.kind,
258
+ flow_type: final_type,
259
+ const_value:,
260
+ id: @preassigned_local_binding_ids[statement.object_id],
261
+ )
262
+ record_declaration_binding(statement, current_scope[statement.name])
263
+ end
264
+ end
265
+
266
+ def check_local_decl_destructure(statement, scopes:, return_type:, allow_return:)
267
+ value_type = infer_expression(statement.value, scopes:)
268
+
269
+ if statement.destructure_type_name
270
+ check_local_decl_struct_destructure(statement, value_type, scopes:)
271
+ elsif value_type.is_a?(Types::Tuple)
272
+ check_local_decl_tuple_destructure(statement, value_type, scopes:)
273
+ else
274
+ raise_sema_error("destructure requires a tuple or struct, got #{value_type}")
275
+ end
276
+ end
277
+
278
+ def check_local_decl_tuple_destructure(statement, value_type, scopes:)
279
+ raise_sema_error("destructure pattern has #{statement.destructure_bindings.length} bindings but tuple has #{value_type.element_types.length} elements") unless statement.destructure_bindings.length == value_type.element_types.length
280
+
281
+ current_scope = current_actual_scope(scopes)
282
+ statement.destructure_bindings.each_with_index do |name, index|
283
+ next if name == "_"
284
+ raise_sema_error("duplicate local #{name} in destructure") if current_scope.key?(name)
285
+ ensure_non_reserved_primitive_name!(name, kind_label: "local", line: statement.line, column: statement.column)
286
+ field_type = value_type.element_types[index]
287
+ current_scope[name] = value_binding(
288
+ name:,
289
+ type: field_type,
290
+ mutable: false,
291
+ kind: :let,
292
+ id: @preassigned_local_binding_ids[statement.object_id],
293
+ )
294
+ record_declaration_binding(statement, current_scope[name])
295
+ end
296
+ end
297
+
298
+ def check_local_decl_struct_destructure(statement, value_type, scopes:)
299
+ type_name = statement.destructure_type_name
300
+ struct_type = if type_name.is_a?(Array)
301
+ resolve_qualified_type_name(type_name)
302
+ else
303
+ @ctx.types[type_name]
304
+ end
305
+ display_name = type_name.is_a?(Array) ? type_name.join(".") : type_name
306
+ raise_sema_error("unknown type #{display_name} for struct destructure") unless struct_type
307
+ raise_sema_error("#{display_name} is not a struct") unless struct_type.is_a?(Types::Struct) || struct_type.is_a?(Types::Tuple)
308
+ ensure_assignable!(value_type, struct_type, "cannot destructure #{value_type} as #{display_name}")
309
+
310
+ fields = struct_type.fields
311
+ raise_sema_error("destructure pattern has #{statement.destructure_bindings.length} bindings but #{display_name} has #{fields.length} fields") unless statement.destructure_bindings.length == fields.length
312
+
313
+ current_scope = current_actual_scope(scopes)
314
+ statement.destructure_bindings.each do |name|
315
+ next if name == "_"
316
+ raise_sema_error("duplicate local #{name} in destructure") if current_scope.key?(name)
317
+ raise_sema_error("unknown field #{display_name}.#{name}") unless fields.key?(name)
318
+ ensure_non_reserved_primitive_name!(name, kind_label: "local", line: statement.line, column: statement.column)
319
+ field_type = fields[name]
320
+ current_scope[name] = value_binding(
321
+ name:,
322
+ type: field_type,
323
+ mutable: false,
324
+ kind: :let,
325
+ id: @preassigned_local_binding_ids[statement.object_id],
326
+ )
327
+ record_declaration_binding(statement, current_scope[name])
328
+ end
329
+ end
330
+
331
+ def resolve_qualified_type_name(parts)
332
+ return nil unless parts.length >= 2
333
+
334
+ if @ctx.imports.key?(parts.first)
335
+ imported_module = @ctx.imports.fetch(parts.first)
336
+ type = imported_module.types[parts.last]
337
+ if imported_module.private_type?(parts.last)
338
+ raise_sema_error("#{parts.first}.#{parts.last} is private to module #{imported_module.name}")
339
+ end
340
+ raise_sema_error("unknown type #{parts.join('.')} for struct destructure") unless type
341
+ return type
342
+ end
343
+
344
+ parent_type = @ctx.types[parts.first]
345
+ if parent_type.respond_to?(:nested_types) && parent_type.nested_types.key?(parts.last)
346
+ return parent_type.nested_types[parts.last]
347
+ end
348
+
349
+ raise_sema_error("unknown type #{parts.join('.')} for struct destructure")
350
+ end
351
+
352
+ def check_local_decl_let_else(statement, scopes:, return_type:, allow_return:,
353
+ discard_binding:, declared_type:, inferred_type:)
354
+ success_type = let_else_success_type(inferred_type)
355
+ error_type = let_else_error_type(inferred_type)
356
+
357
+ if statement.recovered_else
358
+ success_type ||= declared_type || @error_type
359
+ error_type ||= @error_type if statement.else_binding
360
+ else
361
+ raise_sema_error("let-else initializer for #{statement.name} must be nullable, Option[T], or Result[T, E], got #{inferred_type}") unless success_type
362
+ end
363
+
364
+ if discard_binding && declared_type
365
+ raise_sema_error("let-else discard binding _ cannot have a type annotation")
366
+ end
367
+
368
+ if discard_binding && statement.kind == :var
369
+ raise_sema_error("var-else discard binding _ is not allowed")
370
+ end
371
+
372
+ if statement.else_binding && !error_type
373
+ raise_sema_error("let-else error binding for #{statement.name} requires Result[T, E], got #{inferred_type}")
374
+ end
375
+
376
+ if declared_type && let_else_source_type?(declared_type)
377
+ raise_sema_error("let-else type annotation for #{statement.name} must be the success type, got #{declared_type}")
378
+ end
379
+
380
+ if declared_type
381
+ validate_local_ref_type!(declared_type, statement.name)
382
+ validate_local_proc_type!(declared_type, statement.name, initializer: statement.value)
383
+ ensure_assignable!(
384
+ success_type, declared_type,
385
+ "cannot assign #{success_type} to #{statement.name}: expected #{declared_type}",
386
+ expression: statement.value, scopes:,
387
+ contextual_int_to_float: contextual_int_to_float_target?(declared_type),
388
+ line: statement.line, column: statement.column,
389
+ )
390
+ final_type = declared_type
391
+ else
392
+ raise_sema_error("cannot bind void result to #{statement.name}") if success_type.void? && !discard_binding
393
+ final_type = success_type
394
+ end
395
+
396
+ unless discard_binding
397
+ validate_local_ref_type!(final_type, statement.name)
398
+ validate_local_proc_type!(final_type, statement.name, initializer: statement.value)
399
+ end
400
+
401
+ else_scopes = scopes
402
+ if statement.else_binding
403
+ ensure_non_reserved_primitive_name!(statement.else_binding.name, kind_label: "let-else error binding", line: statement.else_binding.line, column: statement.else_binding.column)
404
+ else_binding = value_binding(
405
+ name: statement.else_binding.name, type: error_type, mutable: false, kind: :let,
406
+ id: preassigned_local_binding_id_for(statement.else_binding),
407
+ )
408
+ record_declaration_binding(statement.else_binding, else_binding)
409
+ else_scopes = scopes + [{ statement.else_binding.name => else_binding }]
410
+ end
411
+
412
+ check_block(statement.else_body, scopes: else_scopes, return_type:, allow_return:) if statement.else_body
413
+ if statement.else_body && !statement.recovered_else
414
+ terminator = if inside_loop?
415
+ ControlFlow::Termination.block_always_terminates_in_loop?(statement.else_body)
416
+ else
417
+ cfg_block_always_terminates?(statement.else_body)
418
+ end
419
+ unless terminator
420
+ raise_sema_error("else block for #{statement.name} must exit control flow at line #{statement.line}")
421
+ end
422
+ end
423
+
424
+ storage_type = statement.kind == :var ? final_type : inferred_type
425
+ [storage_type, final_type, nil]
426
+ end
427
+
428
+ def check_local_decl_plain(statement, scopes:, discard_binding:, declared_type:, inferred_type:)
429
+ if declared_type
430
+ validate_local_ref_type!(declared_type, statement.name)
431
+ validate_local_proc_type!(declared_type, statement.name, initializer: statement.value)
432
+ ensure_assignable!(
433
+ inferred_type, declared_type,
434
+ "cannot assign #{inferred_type} to #{statement.name}: expected #{declared_type}",
435
+ expression: statement.value, scopes:,
436
+ contextual_int_to_float: contextual_int_to_float_target?(declared_type),
437
+ line: statement.line, column: statement.column,
438
+ )
439
+ final_type = declared_type
440
+ else
441
+ raise_sema_error("cannot infer type for #{statement.name} from null") if inferred_type.is_a?(Types::Null)
442
+ raise_sema_error("cannot bind void result to #{statement.name}") if inferred_type.void?
443
+ final_type = inferred_type
444
+ end
445
+
446
+ validate_local_ref_type!(final_type, statement.name)
447
+ validate_local_proc_type!(final_type, statement.name, initializer: statement.value)
448
+
449
+ const_value = statement.kind == :let && statement.value ? evaluate_compile_time_const_value(statement.value, scopes:) : nil
450
+ [final_type, final_type, const_value]
451
+ end
452
+
453
+ def check_assignment(statement, scopes:)
454
+ if statement.operator == "=" &&
455
+ statement.target.is_a?(AST::IndexAccess) &&
456
+ statement.target.index.is_a?(AST::RangeExpr)
457
+ return check_range_index_assignment(statement, scopes:)
458
+ end
459
+
460
+ target_type = infer_lvalue(statement.target, scopes:)
461
+ raise_sema_error("cannot assign to non-copyable event storage type #{target_type}") if noncopyable_event_storage_type?(target_type)
462
+
463
+ validate_consuming_foreign_expression!(statement.value, scopes:, root_allowed: false)
464
+ value_type = infer_expression(statement.value, scopes:, expected_type: target_type)
465
+
466
+ case statement.operator
467
+ when "="
468
+ ensure_assignable!(
469
+ value_type,
470
+ target_type,
471
+ "cannot assign #{value_type} to #{target_type}",
472
+ expression: statement.value,
473
+ external_numeric: external_numeric_assignment_target?(statement.target, scopes:),
474
+ contextual_int_to_float: contextual_int_to_float_target?(target_type),
475
+ line: statement.line, column: statement.column,
476
+ )
477
+ when "+=", "-=", "*=", "/="
478
+ raise_sema_error("operator #{statement.operator} requires matching numeric types, got #{target_type} and #{value_type}") unless target_type.numeric? && value_type.numeric?
479
+
480
+ ensure_assignable!(
481
+ value_type,
482
+ target_type,
483
+ "operator #{statement.operator} requires matching numeric types, got #{target_type} and #{value_type}",
484
+ expression: statement.value,
485
+ contextual_int_to_float: contextual_int_to_float_target?(target_type),
486
+ line: statement.line, column: statement.column,
487
+ )
488
+ when "%="
489
+ unless common_integer_type(target_type, value_type) == target_type
490
+ raise_sema_error("operator #{statement.operator} requires compatible integer types, got #{target_type} and #{value_type}")
491
+ end
492
+ when "&=", "|=", "^="
493
+ unless target_type == value_type && bitwise_type?(target_type)
494
+ raise_sema_error("operator #{statement.operator} requires matching integer or flags types, got #{target_type} and #{value_type}")
495
+ end
496
+ when "<<=", ">>="
497
+ unless target_type.is_a?(Types::Primitive) && target_type.integer? && value_type.is_a?(Types::Primitive) && value_type.integer?
498
+ raise_sema_error("operator #{statement.operator} requires integer operands, got #{target_type} and #{value_type}")
499
+ end
500
+ else
501
+ raise_sema_error("unsupported assignment operator #{statement.operator}")
502
+ end
503
+ end
504
+
505
+ def check_range_index_assignment(statement, scopes:)
506
+ target = statement.target
507
+ range = target.index
508
+
509
+ raise_sema_error("range index assignment requires an expression list on the right-hand side") unless statement.value.is_a?(AST::ExpressionList)
510
+ raise_sema_error("range index assignment requires integer literal bounds") unless range.start_expr.is_a?(AST::IntegerLiteral) && range.end_expr.is_a?(AST::IntegerLiteral)
511
+
512
+ start_val = range.start_expr.value
513
+ end_val = range.end_expr.value
514
+ raise_sema_error("range start must be less than end in range index assignment") unless start_val < end_val
515
+
516
+ count = end_val - start_val
517
+ raise_sema_error("range index assignment: range [#{start_val}..#{end_val}) spans #{count} elements but tuple has #{statement.value.elements.length}") unless statement.value.elements.length == count
518
+
519
+ receiver_type = infer_lvalue_receiver(
520
+ target.receiver,
521
+ scopes:,
522
+ allow_pointer_identifier: true,
523
+ require_mutable_pointer: true,
524
+ allow_span_param_identifier: true,
525
+ )
526
+ element_type = infer_index_result_type(receiver_type, @ctx.types.fetch("ptr_uint"))
527
+
528
+ statement.value.elements.each_with_index do |elem, i|
529
+ elem = elem.is_a?(AST::Argument) ? elem.value : elem
530
+ validate_consuming_foreign_expression!(elem, scopes:, root_allowed: false)
531
+ elem_type = infer_expression(elem, scopes:, expected_type: element_type)
532
+ ensure_assignable!(
533
+ elem_type,
534
+ element_type,
535
+ "range index assignment element #{i}: cannot assign #{elem_type} to #{element_type}",
536
+ expression: elem,
537
+ contextual_int_to_float: contextual_int_to_float_target?(element_type),
538
+ line: statement.line,
539
+ )
540
+ end
541
+ end
542
+
543
+ def check_match_stmt(statement, scopes:, return_type:, allow_return:)
544
+ validate_consuming_foreign_expression!(statement.expression, scopes:, root_allowed: false)
545
+ scrutinee_type = infer_expression(statement.expression, scopes:)
546
+ if error_type?(scrutinee_type)
547
+ check_recovered_match_stmt(statement, scopes:, return_type:, allow_return:)
548
+ elsif scrutinee_type.is_a?(Types::Enum)
549
+ check_enum_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
550
+ elsif scrutinee_type.is_a?(Types::Variant)
551
+ check_variant_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
552
+ elsif integer_type?(scrutinee_type)
553
+ check_integer_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
554
+ elsif scrutinee_type.is_a?(Types::StringView)
555
+ check_string_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
556
+ elsif scrutinee_type.is_a?(Types::Tuple)
557
+ check_tuple_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
558
+ else
559
+ raise_sema_error("match requires an enum, variant, or integer scrutinee, got #{scrutinee_type}")
560
+ end
561
+ end
562
+
563
+ def check_enum_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
564
+ each_enum_match_arm(statement, scrutinee_type, scopes:) do |arm, arm_scopes|
565
+ check_block(arm.body, scopes: arm_scopes, return_type:, allow_return:)
566
+ end
567
+ end
568
+
569
+ def each_enum_match_arm(statement, scrutinee_type, scopes:)
570
+ covered_members = {}
571
+ wildcard_seen = false
572
+ statement.arms.each do |arm|
573
+ if arm.pattern.is_a?(AST::ErrorExpr)
574
+ yield arm, scopes
575
+ next
576
+ end
577
+
578
+ if wildcard_pattern?(arm.pattern)
579
+ raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
580
+ wildcard_seen = true
581
+ yield arm, scopes
582
+ next
583
+ end
584
+ validate_consuming_foreign_expression!(arm.pattern, scopes:, root_allowed: false)
585
+ validate_hoistable_foreign_expression!(arm.pattern, scopes:, root_hoistable: false)
586
+ pattern_type = infer_expression(arm.pattern, scopes:, expected_type: scrutinee_type)
587
+ ensure_assignable!(pattern_type, scrutinee_type, "match arm expects #{scrutinee_type}, got #{pattern_type}")
588
+
589
+ member_name = match_member_name(arm.pattern, scrutinee_type)
590
+ raise_sema_error("match arm must be an enum member of #{scrutinee_type}") unless member_name
591
+ raise_sema_error("duplicate match arm #{scrutinee_type}.#{member_name}") if covered_members.key?(member_name)
592
+
593
+ covered_members[member_name] = true
594
+ yield arm, scopes
595
+ end
596
+
597
+ return if wildcard_seen
598
+
599
+ missing_members = scrutinee_type.members - covered_members.keys
600
+ return if missing_members.empty?
601
+
602
+ raise_sema_error("match on #{scrutinee_type} is missing cases: #{missing_members.join(', ')}")
603
+ end
604
+
605
+ def check_integer_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
606
+ has_wildcard = statement.arms.any? { |arm| wildcard_pattern?(arm.pattern) }
607
+ raise_sema_error("match on integer type #{scrutinee_type} requires a wildcard arm (_:)") unless has_wildcard
608
+
609
+ covered_values = {}
610
+ wildcard_seen = false
611
+ statement.arms.each do |arm|
612
+ if arm.pattern.is_a?(AST::ErrorExpr)
613
+ check_recovered_match_arm_body(arm, scopes:, return_type:, allow_return:)
614
+ next
615
+ end
616
+
617
+ if wildcard_pattern?(arm.pattern)
618
+ raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
619
+ wildcard_seen = true
620
+ check_block(arm.body, scopes:, return_type:, allow_return:)
621
+ next
622
+ end
623
+ unless arm.pattern.is_a?(AST::IntegerLiteral) || arm.pattern.is_a?(AST::CharLiteral)
624
+ raise_sema_error("match arm for integer scrutinee must be an integer literal, char literal, or _, got #{arm.pattern.class.name}")
625
+ end
626
+ value = arm.pattern.value
627
+ raise_sema_error("duplicate match arm value #{value}") if covered_values.key?(value)
628
+ covered_values[value] = true
629
+ check_block(arm.body, scopes:, return_type:, allow_return:)
630
+ end
631
+ end
632
+
633
+ def check_string_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
634
+ has_wildcard = statement.arms.any? { |arm| wildcard_pattern?(arm.pattern) }
635
+ raise_sema_error("match on str requires a wildcard arm (_:)") unless has_wildcard
636
+
637
+ covered_values = {}
638
+ wildcard_seen = false
639
+ statement.arms.each do |arm|
640
+ if arm.pattern.is_a?(AST::ErrorExpr)
641
+ check_recovered_match_arm_body(arm, scopes:, return_type:, allow_return:)
642
+ next
643
+ end
644
+
645
+ if wildcard_pattern?(arm.pattern)
646
+ raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
647
+ wildcard_seen = true
648
+ check_block(arm.body, scopes:, return_type:, allow_return:)
649
+ next
650
+ end
651
+ unless arm.pattern.is_a?(AST::StringLiteral)
652
+ raise_sema_error("match arm for str scrutinee must be a string literal or _, got #{arm.pattern.class.name}")
653
+ end
654
+ value = arm.pattern.value
655
+ raise_sema_error("duplicate match arm value \"#{value}\"") if covered_values.key?(value)
656
+ covered_values[value] = true
657
+ check_block(arm.body, scopes:, return_type:, allow_return:)
658
+ end
659
+ end
660
+
661
+ def check_tuple_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
662
+ has_wildcard = statement.arms.any? { |arm| wildcard_pattern?(arm.pattern) }
663
+ raise_sema_error("match on tuple type #{scrutinee_type} requires a wildcard arm (_:)") unless has_wildcard
664
+
665
+ arity = scrutinee_type.element_types.length
666
+ covered_values = {}
667
+ wildcard_seen = false
668
+
669
+ statement.arms.each do |arm|
670
+ if arm.pattern.is_a?(AST::ErrorExpr)
671
+ check_recovered_match_arm_body(arm, scopes:, return_type:, allow_return:)
672
+ next
673
+ end
674
+
675
+ if wildcard_pattern?(arm.pattern)
676
+ raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
677
+ wildcard_seen = true
678
+ check_block(arm.body, scopes:, return_type:, allow_return:)
679
+ next
680
+ end
681
+
682
+ unless arm.pattern.is_a?(AST::ExpressionList)
683
+ raise_sema_error("match arm for tuple scrutinee must be a tuple literal or _, got #{arm.pattern.class.name}")
684
+ end
685
+
686
+ elements = arm.pattern.elements
687
+ raise_sema_error("tuple match arm has #{elements.length} elements but scrutinee has #{arity}") unless elements.length == arity
688
+
689
+ values = elements.map do |elem|
690
+ if elem.is_a?(AST::Identifier) && elem.name == "_"
691
+ :wildcard
692
+ elsif elem.is_a?(AST::IntegerLiteral) || elem.is_a?(AST::CharLiteral) ||
693
+ elem.is_a?(AST::StringLiteral) || elem.is_a?(AST::BooleanLiteral)
694
+ elem.value
695
+ else
696
+ raise_sema_error("tuple match arm element must be a literal or _, got #{elem.class.name}")
697
+ end
698
+ end
699
+
700
+ all_literal = values.all? { |v| v != :wildcard }
701
+ if all_literal
702
+ raise_sema_error("duplicate tuple match arm #{arm.pattern.elements.map { |e| e.respond_to?(:value) ? e.value.inspect : '_' }.join(', ')}") if covered_values.key?(values)
703
+ covered_values[values] = true
704
+ end
705
+
706
+ check_block(arm.body, scopes:, return_type:, allow_return:)
707
+ end
708
+ end
709
+
710
+ def wildcard_pattern?(expression)
711
+ expression.is_a?(AST::Identifier) && expression.name == "_"
712
+ end
713
+
714
+ def let_else_discard_binding_syntax?(statement)
715
+ statement.is_a?(AST::LocalDecl) && (statement.else_body || statement.recovered_else) && statement.name == "_"
716
+ end
717
+
718
+ def let_else_source_type?(type)
719
+ type.is_a?(Types::Nullable) || result_let_else_type?(type) || option_let_else_type?(type)
720
+ end
721
+
722
+ def let_else_success_type(type)
723
+ return @error_type if error_type?(type)
724
+ return type.base if type.is_a?(Types::Nullable)
725
+ return type.arm("some").fetch("value") if option_let_else_type?(type)
726
+ return unless result_let_else_type?(type)
727
+
728
+ type.arm("success").fetch("value")
729
+ end
730
+
731
+ def let_else_error_type(type)
732
+ return @error_type if error_type?(type)
733
+ return unless result_let_else_type?(type)
734
+
735
+ type.arm("failure").fetch("error")
736
+ end
737
+
738
+ def option_let_else_type?(type)
739
+ return false unless type.is_a?(Types::Variant)
740
+
741
+ some_fields = type.arm("some")
742
+ none_fields = type.arm("none")
743
+ some_fields && some_fields.length == 1 && some_fields.key?("value") &&
744
+ none_fields && none_fields.empty?
745
+ end
746
+
747
+ def result_let_else_type?(type)
748
+ return false unless type.is_a?(Types::Variant)
749
+
750
+ success_fields = type.arm("success")
751
+ failure_fields = type.arm("failure")
752
+ success_fields && success_fields.length == 1 && success_fields.key?("value") &&
753
+ failure_fields && failure_fields.length == 1 && failure_fields.key?("error")
754
+ end
755
+
756
+ def check_variant_match_stmt(statement, scrutinee_type, scopes:, return_type:, allow_return:)
757
+ each_variant_match_arm(statement, scrutinee_type, scopes:) do |arm, arm_scopes|
758
+ check_block(arm.body, scopes: arm_scopes, return_type:, allow_return:)
759
+ end
760
+ end
761
+
762
+ def each_variant_match_arm(statement, scrutinee_type, scopes:)
763
+ covered_arms = {}
764
+ wildcard_seen = false
765
+ equality_cover_table = {}
766
+ statement.arms.each do |arm|
767
+ if arm.pattern.is_a?(AST::ErrorExpr)
768
+ yield arm, scopes
769
+ next
770
+ end
771
+
772
+ if wildcard_pattern?(arm.pattern)
773
+ raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
774
+ wildcard_seen = true
775
+ yield arm, scopes
776
+ next
777
+ end
778
+ validate_consuming_foreign_expression!(arm.pattern, scopes:, root_allowed: false)
779
+ validate_hoistable_foreign_expression!(arm.pattern, scopes:, root_hoistable: false)
780
+
781
+ arm_name = variant_match_arm_name(arm.pattern, scrutinee_type)
782
+ raise_sema_error("match arm must be a variant arm of #{scrutinee_type}") unless arm_name
783
+
784
+ arm_scopes = scopes.dup
785
+ has_guards = false
786
+ equality_cover = {}
787
+
788
+ if arm.pattern.is_a?(AST::Call) && !arm.pattern.arguments.empty?
789
+ has_guards, equality_cover = check_struct_match_pattern(arm.pattern.arguments, arm_name, scrutinee_type, arm_scopes, scopes:, arm:)
790
+ end
791
+
792
+ unless has_guards
793
+ raise_sema_error("duplicate match arm #{scrutinee_type}.#{arm_name}") if covered_arms.key?(arm_name)
794
+ covered_arms[arm_name] = true
795
+ else
796
+ equality_cover_table[arm_name] = merge_equality_cover(equality_cover_table[arm_name], equality_cover)
797
+ end
798
+
799
+ if arm.binding_name
800
+ ensure_non_reserved_primitive_name!(arm.binding_name, kind_label: "match binding", line: arm.binding_line, column: arm.binding_column)
801
+ fields = scrutinee_type.arm(arm_name)
802
+ if fields.nil? || fields.empty?
803
+ raise_sema_error("variant arm #{scrutinee_type}.#{arm_name} has no payload; 'as' binding is not allowed")
804
+ end
805
+
806
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
807
+ binding = value_binding(
808
+ name: arm.binding_name,
809
+ type: payload_type,
810
+ mutable: true,
811
+ kind: :local,
812
+ id: @preassigned_local_binding_ids[arm.object_id],
813
+ )
814
+ arm_scopes = arm_scopes + [{ arm.binding_name => binding }]
815
+ record_declaration_binding(arm, binding)
816
+ end
817
+ yield arm, arm_scopes
818
+ end
819
+
820
+ equality_cover_table.each do |arm_name, field_covers|
821
+ next if covered_arms.key?(arm_name)
822
+ payload_fields = scrutinee_type.arm(arm_name)
823
+ next unless payload_fields
824
+ covered = field_covers.any? do |field_name, covered_members|
825
+ field_type = payload_fields[field_name]
826
+ next unless field_type.is_a?(Types::Enum)
827
+ all_members = field_type.members
828
+ all_members.any? && (all_members - covered_members).empty?
829
+ end
830
+ covered_arms[arm_name] = true if covered
831
+ end
832
+
833
+ return if wildcard_seen
834
+
835
+ missing_arms = scrutinee_type.arm_names - covered_arms.keys
836
+ return if missing_arms.empty?
837
+
838
+ raise_sema_error("match on #{scrutinee_type} is missing cases: #{missing_arms.join(', ')}")
839
+ end
840
+
841
+ def merge_equality_cover(existing, new_cover)
842
+ return new_cover unless existing
843
+ merged = existing.dup
844
+ new_cover.each do |field, members|
845
+ merged[field] = (merged[field] || []) + members
846
+ end
847
+ merged
848
+ end
849
+
850
+ def check_recovered_match_stmt(statement, scopes:, return_type:, allow_return:)
851
+ statement.arms.each do |arm|
852
+ check_recovered_match_arm_body(arm, scopes:, return_type:, allow_return:)
853
+ end
854
+ end
855
+
856
+ def check_recovered_match_arm_body(arm, scopes:, return_type:, allow_return:)
857
+ arm_scopes = scopes.dup
858
+ if arm.binding_name
859
+ ensure_non_reserved_primitive_name!(arm.binding_name, kind_label: "match binding", line: arm.binding_line, column: arm.binding_column)
860
+ binding = value_binding(
861
+ name: arm.binding_name,
862
+ type: @error_type,
863
+ mutable: true,
864
+ kind: :local,
865
+ id: @preassigned_local_binding_ids.fetch(arm.object_id),
866
+ )
867
+ arm_scopes = arm_scopes + [{ arm.binding_name => binding }]
868
+ record_declaration_binding(arm, binding)
869
+ end
870
+ check_block(arm.body, scopes: arm_scopes, return_type:, allow_return:)
871
+ end
872
+
873
+ def variant_match_arm_name(pattern, scrutinee_type)
874
+ # Pattern must be `TypeName.arm_name` or `module.TypeName.arm_name`
875
+ # For struct patterns, the pattern is Call(MemberAccess(...), args) — unwrap the callee
876
+ callee = case pattern
877
+ when AST::Call
878
+ pattern.callee
879
+ else
880
+ pattern
881
+ end
882
+ return nil unless callee.is_a?(AST::MemberAccess)
883
+
884
+ member = callee.member
885
+ return nil unless scrutinee_type.arm_names.include?(member)
886
+
887
+ # Verify the receiver resolves to the scrutinee variant type
888
+ receiver_type = resolve_type_expression(callee.receiver)
889
+ return member if receiver_type == scrutinee_type
890
+
891
+ if scrutinee_type.is_a?(Types::VariantInstance) && receiver_type.is_a?(Types::GenericVariantDefinition)
892
+ return member if receiver_type == scrutinee_type.definition
893
+ end
894
+
895
+ return nil unless scrutinee_type.is_a?(Types::VariantInstance) && receiver_type.is_a?(Types::Variant)
896
+ return nil unless receiver_type.name == scrutinee_type.name && receiver_type.module_name == scrutinee_type.module_name
897
+
898
+ member
899
+ end
900
+
901
+ def check_struct_match_pattern(arguments, arm_name, scrutinee_type, arm_scopes, scopes:, arm:)
902
+ payload_fields = scrutinee_type.arm(arm_name)
903
+ raise_sema_error("variant arm #{scrutinee_type}.#{arm_name} has no payload fields for struct pattern") if payload_fields.nil? || payload_fields.empty?
904
+
905
+ # Nested struct detection: if arm has exactly one field whose type is a struct,
906
+ # auto-destructure through to the struct's own fields, but only when none of
907
+ # the arguments reference the arm's own field name directly.
908
+ if payload_fields.size == 1
909
+ native_field = payload_fields.keys.first
910
+ has_native_reference = arguments.any? do |arg|
911
+ next false if !arg.name && arg.value.is_a?(AST::Identifier) && arg.value.name == "_"
912
+ name = arg.name || (arg.value.is_a?(AST::Identifier) ? arg.value.name : nil)
913
+ name == native_field
914
+ end
915
+ unless has_native_reference
916
+ single_field_type = payload_fields.values.first
917
+ payload_fields = single_field_type.fields if single_field_type.is_a?(Types::Struct)
918
+ end
919
+ end
920
+
921
+ has_guards = false
922
+ seen_fields = {}
923
+ equality_cover = {}
924
+
925
+ arguments.each do |arg|
926
+ if arg.value.is_a?(AST::Identifier) && arg.value.name == "_"
927
+ # _ discard: skip this field, no binding, no guard
928
+ next
929
+ elsif arg.name
930
+ # Equality pattern: kind = Kind.boss
931
+ field_name = arg.name
932
+ raise_sema_error("unknown field #{scrutinee_type}.#{arm_name}.#{field_name}") unless payload_fields.key?(field_name)
933
+ raise_sema_error("duplicate field #{field_name} in struct pattern") if seen_fields.key?(field_name)
934
+ seen_fields[field_name] = true
935
+ has_guards = true
936
+
937
+ field_type = payload_fields[field_name]
938
+ actual_type = infer_expression(arg.value, scopes:, expected_type: field_type)
939
+ ensure_assignable!(actual_type, field_type, "field #{field_name} expects #{field_type}, got #{actual_type}", expression: arg.value)
940
+
941
+ if field_type.is_a?(Types::Enum) && arg.value.is_a?(AST::MemberAccess)
942
+ equality_cover[field_name] = [] unless equality_cover.key?(field_name)
943
+ equality_cover[field_name] << arg.value.member
944
+ end
945
+ elsif arg.value.is_a?(AST::Identifier)
946
+ # Binding: position
947
+ field_name = arg.value.name
948
+ raise_sema_error("unknown field #{scrutinee_type}.#{arm_name}.#{field_name}") unless payload_fields.key?(field_name)
949
+ raise_sema_error("duplicate field #{field_name} in struct pattern") if seen_fields.key?(field_name)
950
+ seen_fields[field_name] = true
951
+
952
+ field_type = payload_fields[field_name]
953
+ binding = value_binding(
954
+ name: field_name,
955
+ type: field_type,
956
+ mutable: false,
957
+ kind: :local,
958
+ id: @preassigned_local_binding_ids[arg.object_id],
959
+ )
960
+ arm_scopes.last[field_name] = binding
961
+ elsif arg.value.is_a?(AST::BinaryOp) && arg.value.left.is_a?(AST::Identifier)
962
+ # Guard: hp > 0
963
+ field_name = arg.value.left.name
964
+ raise_sema_error("unknown field #{scrutinee_type}.#{arm_name}.#{field_name}") unless payload_fields.key?(field_name)
965
+ raise_sema_error("duplicate field #{field_name} in struct pattern") if seen_fields.key?(field_name)
966
+ seen_fields[field_name] = true
967
+ has_guards = true
968
+
969
+ field_type = payload_fields[field_name]
970
+ comparison_operators = ["==", "!=", "<", "<=", ">", ">="]
971
+ unless comparison_operators.include?(arg.value.operator)
972
+ raise_sema_error("unsupported guard operator '#{arg.value.operator}' in struct pattern; use ==, !=, <, <=, >, or >=", arg.value)
973
+ end
974
+
975
+ operand_type = infer_expression(arg.value.right, scopes:, expected_type: field_type)
976
+ ensure_assignable!(operand_type, field_type, "guard comparison expects #{field_type}, got #{operand_type}", expression: arg.value.right)
977
+ else
978
+ raise_sema_error("invalid field pattern in struct match arm; expected field name, comparison, or equality", expression: arg.value)
979
+ end
980
+ end
981
+
982
+ [has_guards, equality_cover]
983
+ end
984
+
985
+ def check_for_stmt(statement, scopes:, return_type:, allow_return:)
986
+ if statement.threaded
987
+ check_threaded_for_stmt(statement, scopes:, return_type:, allow_return:)
988
+ return
989
+ end
990
+
991
+ statement.iterables.each do |iterable|
992
+ validate_consuming_foreign_expression!(iterable, scopes:, root_allowed: false)
993
+ end
994
+
995
+ raise_sema_error("for loop binder count must match iterable count") unless statement.bindings.length == statement.iterables.length
996
+
997
+ binding_infos = if statement.parallel?
998
+ check_parallel_for_bindings(statement, scopes:)
999
+ else
1000
+ iterable_type = nil
1001
+ loop_type = if range_expr?(statement.iterable)
1002
+ check_range_expr_loop(statement.iterable, scopes:)
1003
+ else
1004
+ iterable_type = infer_expression(statement.iterable, scopes:)
1005
+ collection_loop_type(iterable_type) || iterator_loop_type(iterable_type)
1006
+ end
1007
+
1008
+ raise_sema_error("for loop expects start..stop, array[T, N], span[T], or an iterable with iter()/next()") unless loop_type
1009
+
1010
+ binding_type = if iterable_type
1011
+ collection_loop_binding_type(iterable_type, loop_type) || loop_type
1012
+ else
1013
+ loop_type
1014
+ end
1015
+ [{ binding: statement.binding, type: binding_type }]
1016
+ end
1017
+
1018
+ with_nested_scope(scopes) do |loop_scopes|
1019
+ binding_infos.each do |entry|
1020
+ binding = entry[:binding]
1021
+ ensure_non_reserved_primitive_name!(binding.name, kind_label: "for binding", line: binding.line, column: binding.column)
1022
+ current_actual_scope(loop_scopes)[binding.name] = value_binding(
1023
+ name: binding.name,
1024
+ type: entry[:type],
1025
+ mutable: false,
1026
+ kind: :let,
1027
+ id: @preassigned_local_binding_ids[binding.object_id],
1028
+ )
1029
+ record_declaration_binding(binding, current_actual_scope(loop_scopes)[binding.name])
1030
+ end
1031
+ with_loop do
1032
+ check_block(statement.body, scopes: loop_scopes, return_type:, allow_return:)
1033
+ end
1034
+ end
1035
+ end
1036
+
1037
+ def check_parallel_for_bindings(statement, scopes:)
1038
+ raise_sema_error("parallel for loops currently support arrays and spans only") if statement.iterables.any? { |iterable| range_expr?(iterable) }
1039
+
1040
+ iterable_types = statement.iterables.map { |iterable| infer_expression(iterable, scopes:) }
1041
+ binding_infos = iterable_types.each_with_index.map do |iterable_type, index|
1042
+ loop_type = collection_loop_type(iterable_type)
1043
+ raise_sema_error("parallel for loops expect arrays or spans for each iterable") unless loop_type
1044
+
1045
+ binding_type = collection_loop_binding_type(iterable_type, loop_type) || loop_type
1046
+ { binding: statement.bindings[index], iterable_type:, type: binding_type }
1047
+ end
1048
+
1049
+ ensure_parallel_for_static_lengths_match!(binding_infos.map { |entry| entry[:iterable_type] })
1050
+ binding_infos
1051
+ end
1052
+
1053
+ def ensure_parallel_for_static_lengths_match!(iterable_types)
1054
+ lengths = iterable_types.filter_map { |iterable_type| array_type?(iterable_type) ? array_length(iterable_type) : nil }
1055
+ return if lengths.empty? || lengths.all? { |length| length == lengths.first }
1056
+
1057
+ raise_sema_error("parallel for iterables must have matching lengths")
1058
+ end
1059
+
1060
+ def check_threaded_for_stmt(statement, scopes:, return_type:, allow_return:)
1061
+ @uses_parallel_for = true
1062
+ raise_sema_error("parallel for requires a range expression (start..end)") unless range_expr?(statement.iterable)
1063
+
1064
+ loop_type = check_range_expr_loop(statement.iterable, scopes:)
1065
+ validate_threaded_for_body!(statement.body)
1066
+
1067
+ with_nested_scope(scopes) do |loop_scopes|
1068
+ binding = statement.binding
1069
+ ensure_non_reserved_primitive_name!(binding.name, kind_label: "for binding", line: binding.line, column: binding.column)
1070
+ current_actual_scope(loop_scopes)[binding.name] = value_binding(
1071
+ name: binding.name,
1072
+ type: loop_type,
1073
+ mutable: false,
1074
+ kind: :let,
1075
+ id: @preassigned_local_binding_ids[binding.object_id],
1076
+ )
1077
+ record_declaration_binding(binding, current_actual_scope(loop_scopes)[binding.name])
1078
+ check_block(statement.body, scopes: loop_scopes, return_type:, allow_return: false)
1079
+ end
1080
+ end
1081
+
1082
+ def validate_threaded_for_body!(body)
1083
+ body.each { |stmt| validate_threaded_for_statement!(stmt) }
1084
+ end
1085
+
1086
+ def validate_threaded_for_statement!(stmt)
1087
+ case stmt
1088
+ when AST::BreakStmt
1089
+ raise_sema_error("break is not allowed inside parallel for", line: stmt.line, column: stmt.column)
1090
+ when AST::ContinueStmt
1091
+ raise_sema_error("continue is not allowed inside parallel for", line: stmt.line, column: stmt.column)
1092
+ when AST::ReturnStmt
1093
+ raise_sema_error("return is not allowed inside parallel for", line: stmt.line, column: stmt.column)
1094
+ when AST::DeferStmt
1095
+ raise_sema_error("defer is not allowed inside parallel for", line: stmt.line, column: stmt.column)
1096
+ when AST::AwaitExpr
1097
+ raise_sema_error("await is not allowed inside parallel for", line: stmt.line, column: stmt.column)
1098
+ when AST::IfStmt
1099
+ validate_threaded_for_body!(stmt.then_body)
1100
+ stmt.else_if_clauses&.each { |clause| validate_threaded_for_body!(clause.body) }
1101
+ validate_threaded_for_body!(stmt.else_body) if stmt.else_body
1102
+ when AST::WhileStmt
1103
+ validate_threaded_for_body!(stmt.body)
1104
+ when AST::ForStmt
1105
+ raise_sema_error("nested for loops are not allowed inside parallel for", line: stmt.line, column: stmt.column) if stmt.threaded
1106
+ validate_threaded_for_body!(stmt.body)
1107
+ when AST::MatchStmt
1108
+ stmt.arms&.each { |arm| validate_threaded_for_body!(arm.body) }
1109
+ when AST::UnsafeStmt
1110
+ validate_threaded_for_body!(stmt.body) if stmt.body.is_a?(Array)
1111
+ end
1112
+ end
1113
+
1114
+ def check_parallel_block_stmt(statement, scopes:, return_type:)
1115
+ @uses_parallel_for = true
1116
+ raise_sema_error("parallel block must contain at least two statements", line: statement.line, column: statement.column) if statement.bodies.length < 2
1117
+
1118
+ statement.bodies.each do |body|
1119
+ validate_threaded_for_body!(body)
1120
+ check_block(body, scopes:, return_type:, allow_return: false)
1121
+ end
1122
+ end
1123
+
1124
+ def check_gather_stmt(statement, scopes:)
1125
+ raise_sema_error("gather requires at least one handle", line: statement.line, column: statement.column) if statement.handles.empty?
1126
+
1127
+ statement.handles.each do |handle|
1128
+ handle_type = infer_expression(handle, scopes:)
1129
+ raise_sema_error("gather expects Handle, got #{handle_type}", line: statement.line, column: statement.column) unless handle_type.is_a?(Types::Handle)
1130
+ end
1131
+ end
1132
+
1133
+
1134
+ def check_static_assert(statement, scopes:)
1135
+ validate_consuming_foreign_expression!(statement.condition, scopes:, root_allowed: false)
1136
+ validate_consuming_foreign_expression!(statement.message, scopes:, root_allowed: false)
1137
+ validate_hoistable_foreign_expression!(statement.condition, scopes:, root_hoistable: false)
1138
+ validate_hoistable_foreign_expression!(statement.message, scopes:, root_hoistable: false)
1139
+ condition_type = infer_expression(statement.condition, scopes:, expected_type: @ctx.types.fetch("bool"))
1140
+ ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "static_assert condition must be bool, got #{condition_type}")
1141
+ condition_value = evaluate_compile_time_const_value(statement.condition, scopes:)
1142
+ raise_sema_error("static_assert condition must be a compile-time bool constant") unless condition_value == true || condition_value == false
1143
+ raise_sema_error("static_assert message must be a string literal") unless statement.message.is_a?(AST::StringLiteral)
1144
+
1145
+ message_type = infer_expression(statement.message, scopes:, expected_type: @ctx.types.fetch("str"))
1146
+ return if string_like_type?(message_type)
1147
+
1148
+ raise_sema_error("static_assert message must be str or cstr, got #{message_type}")
1149
+ end
1150
+
1151
+ def check_emit_stmt(statement)
1152
+ raise_sema_error("emit is only allowed inside const function or inline blocks") unless @compile_time_depth.positive?
1153
+ end
1154
+
1155
+ def check_range_expr_loop(expression, scopes:)
1156
+ start_type = infer_expression(expression.start_expr, scopes:)
1157
+ stop_type = infer_expression(expression.end_expr, scopes:)
1158
+
1159
+ unless integer_type?(start_type) && integer_type?(stop_type)
1160
+ raise_sema_error("range bounds must be integer types, got #{start_type} and #{stop_type}")
1161
+ end
1162
+
1163
+ if start_type != stop_type
1164
+ if expression.start_expr.is_a?(AST::IntegerLiteral)
1165
+ start_type = infer_expression(expression.start_expr, scopes:, expected_type: stop_type)
1166
+ elsif expression.end_expr.is_a?(AST::IntegerLiteral)
1167
+ stop_type = infer_expression(expression.end_expr, scopes:, expected_type: start_type)
1168
+ end
1169
+ end
1170
+
1171
+ raise_sema_error("range bounds must use matching integer types, got #{start_type} and #{stop_type}") unless start_type == stop_type
1172
+
1173
+ start_type
1174
+ end
1175
+
1176
+ def when_chosen_body(decl)
1177
+ discriminant_value = evaluate_compile_time_const_value(decl.discriminant, scopes: [])
1178
+ return nil if discriminant_value.nil?
1179
+
1180
+ chosen_branch = decl.branches.find do |branch|
1181
+ pattern_value = evaluate_compile_time_const_value(branch.pattern, scopes: [])
1182
+ discriminant_value == pattern_value
1183
+ end
1184
+
1185
+ chosen_branch&.body || decl.else_body
1186
+ end
1187
+
1188
+ def check_when_stmt(statement, scopes:, return_type:, allow_return:)
1189
+ discriminant_value = evaluate_when_discriminant(statement.discriminant)
1190
+
1191
+ chosen_branch = statement.branches.find do |branch|
1192
+ pattern_value = evaluate_compile_time_const_value(branch.pattern, scopes:)
1193
+ discriminant_value == pattern_value
1194
+ end
1195
+
1196
+ if chosen_branch
1197
+ check_block(chosen_branch.body, scopes:, return_type:, allow_return:)
1198
+ elsif statement.else_body
1199
+ check_block(statement.else_body, scopes:, return_type:, allow_return:)
1200
+ else
1201
+ raise_sema_error("when discriminant value #{discriminant_value} does not match any branch and no else is provided")
1202
+ end
1203
+ end
1204
+
1205
+ def evaluate_when_discriminant(expression)
1206
+ value = evaluate_compile_time_const_value(expression, scopes: [])
1207
+ raise_sema_error("when discriminant must be a compile-time constant", expression) if value.nil?
1208
+
1209
+ value
1210
+ end
1211
+
1212
+ def check_inline_for_stmt(statement, scopes:, return_type:, allow_return:)
1213
+ iterable = evaluate_compile_time_const_value(statement.iterables.first, scopes:)
1214
+ raise_sema_error("inline for iterable must be a compile-time constant") unless iterable.is_a?(Array)
1215
+ raise_sema_error("inline for iterable is empty") if iterable.empty?
1216
+
1217
+ loop_var_name = statement.bindings.first.name
1218
+ ensure_non_reserved_primitive_name!(loop_var_name, kind_label: "for binding", line: statement.bindings.first.line, column: statement.bindings.first.column)
1219
+
1220
+ # The loop is unrolled at compile time, so check the body once per element:
1221
+ # each iteration's element type is validated and any per-element
1222
+ # specialization (e.g. `equal[field.type]` for a struct field, or a
1223
+ # `format_value[field.type]` recursion) is discovered by sema rather than
1224
+ # first appearing during lowering.
1225
+ iterable.each do |element|
1226
+ with_nested_scope(scopes) do |loop_scopes|
1227
+ current_actual_scope(loop_scopes)[loop_var_name] = value_binding(
1228
+ name: loop_var_name,
1229
+ type: inline_for_element_type(element),
1230
+ mutable: false,
1231
+ kind: :let,
1232
+ id: @preassigned_local_binding_ids[statement.bindings.first.object_id],
1233
+ const_value: element,
1234
+ )
1235
+ with_loop do
1236
+ with_compile_time do
1237
+ check_block(statement.body, scopes: loop_scopes, return_type:, allow_return:)
1238
+ end
1239
+ end
1240
+ end
1241
+ end
1242
+ end
1243
+
1244
+ def inline_for_element_type(element)
1245
+ if element.is_a?(Types::FieldHandle)
1246
+ builtin_field_handle_type
1247
+ elsif element.is_a?(Types::CallableHandle)
1248
+ builtin_callable_handle_type
1249
+ elsif element.is_a?(Types::AttributeHandle)
1250
+ builtin_attribute_handle_type
1251
+ elsif element.is_a?(Types::MemberHandle)
1252
+ builtin_member_handle_type
1253
+ elsif element.is_a?(Types::StructHandle)
1254
+ builtin_struct_handle_type
1255
+ else
1256
+ @ctx.types.fetch("int")
1257
+ end
1258
+ end
1259
+
1260
+ def check_inline_while_stmt(statement, scopes:, return_type:, allow_return:)
1261
+ condition = evaluate_compile_time_const_value(statement.condition, scopes:)
1262
+ raise_sema_error("inline while condition must be a compile-time constant") if condition.nil?
1263
+
1264
+ with_loop do
1265
+ with_compile_time do
1266
+ check_block(statement.body, scopes:, return_type:, allow_return:)
1267
+ end
1268
+ end
1269
+ end
1270
+
1271
+ def check_inline_if_stmt(statement, scopes:, return_type:, allow_return:)
1272
+ chosen_branch = statement.branches.find do |branch|
1273
+ condition_value = evaluate_compile_time_const_value(branch.condition, scopes:)
1274
+ raise_sema_error("inline if condition must be a compile-time constant") if condition_value.nil?
1275
+ raise_sema_error("inline if condition must be bool") unless condition_value == true || condition_value == false
1276
+ condition_value
1277
+ end
1278
+
1279
+ if chosen_branch
1280
+ with_compile_time do
1281
+ check_block(chosen_branch.body, scopes:, return_type:, allow_return:)
1282
+ end
1283
+ elsif statement.else_body
1284
+ with_compile_time do
1285
+ check_block(statement.else_body, scopes:, return_type:, allow_return:)
1286
+ end
1287
+ end
1288
+ end
1289
+
1290
+ def check_inline_match_stmt(statement, scopes:, return_type:, allow_return:)
1291
+ scrutinee = evaluate_compile_time_const_value(statement.expression, scopes:)
1292
+ raise_sema_error("inline match scrutinee must be a compile-time constant") if scrutinee.nil?
1293
+
1294
+ chosen_arm = statement.arms.find do |arm|
1295
+ pattern_value = evaluate_compile_time_const_value(arm.pattern, scopes:)
1296
+ scrutinee == pattern_value
1297
+ end
1298
+
1299
+ if chosen_arm
1300
+ with_compile_time do
1301
+ check_block(chosen_arm.body, scopes:, return_type:, allow_return:)
1302
+ end
1303
+ end
1304
+ end
1305
+
1306
+ end
1307
+ end
1308
+ end