mt-lang 0.3.4 → 0.3.6

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