mt-lang 0.3.8 → 0.3.10

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