mt-lang 0.3.4 → 0.3.5

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.
@@ -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
@@ -1088,5 +371,756 @@ module MilkTea
1088
371
  field_expr
1089
372
  end
1090
373
  end
374
+
375
+ def lower_block_unsafe_stmt(statement, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
376
+ body = lower_block(
377
+ statement.body,
378
+ env: local_env,
379
+ active_defers: active_defers + local_defers,
380
+ return_type:,
381
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
382
+ allow_return:,
383
+ )
384
+ lowered << IR::BlockStmt.new(body:)
385
+ end
386
+
387
+ def lower_block_when_stmt(statement, lowered:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
388
+ discriminant = compile_time_const_value(statement.discriminant)
389
+ chosen_branch = statement.branches.find do |branch|
390
+ discriminant == compile_time_const_value(branch.pattern)
391
+ end
392
+
393
+ if chosen_branch
394
+ lowered.concat(lower_block(
395
+ chosen_branch.body,
396
+ env: local_env,
397
+ active_defers:,
398
+ return_type:,
399
+ loop_flow:,
400
+ allow_return:,
401
+ ))
402
+ elsif statement.else_body
403
+ lowered.concat(lower_block(
404
+ statement.else_body,
405
+ env: local_env,
406
+ active_defers:,
407
+ return_type:,
408
+ loop_flow:,
409
+ allow_return:,
410
+ ))
411
+ end
412
+ end
413
+
414
+ def lower_block_expression_stmt(statement, lowered:, local_defers:, local_env:)
415
+ if (format_sink_statements = lower_explicit_format_sink_expression_statement(statement.expression, env: local_env, line: statement.line))
416
+ lowered.concat(format_sink_statements)
417
+ return
418
+ end
419
+
420
+ expression_expected_type = if statement.expression.is_a?(AST::UnaryOp) && statement.expression.operator == "?"
421
+ nil
422
+ else
423
+ infer_expression_type(statement.expression, env: local_env)
424
+ end
425
+ prepared_setup, prepared_expression, prepared_cleanups = prepare_expression_with_cleanups(
426
+ statement.expression,
427
+ env: local_env,
428
+ expected_type: expression_expected_type,
429
+ allow_root_statement_foreign: true,
430
+ allow_void_propagation: true,
431
+ )
432
+ lowered.concat(prepared_setup)
433
+ if prepared_expression && (foreign_call = foreign_call_info(prepared_expression, local_env))
434
+ setup, _value = lower_foreign_call_statement(
435
+ foreign_call,
436
+ env: local_env,
437
+ expected_type: foreign_call[:binding].type.return_type,
438
+ statement_position: true,
439
+ discard_result: true,
440
+ )
441
+ lowered.concat(setup)
442
+ lowered.concat(prepared_cleanups.flat_map(&:itself))
443
+ local_env[:scopes] = scopes_with_refinements(local_env[:scopes], consuming_foreign_call_refinements(foreign_call, local_env))
444
+ elsif prepared_expression
445
+ lowered << IR::ExpressionStmt.new(expression: lower_expression(prepared_expression, env: local_env), line: statement.line, source_path: @ctx.current_analysis_path)
446
+ lowered.concat(prepared_cleanups.flat_map(&:itself))
447
+ else
448
+ lowered.concat(prepared_cleanups.flat_map(&:itself))
449
+ end
450
+ end
451
+
452
+ def lower_block_return_stmt(statement, lowered:, local_defers:, local_env:, active_defers:, return_type:, allow_return:)
453
+ raise LoweringError, "return is not allowed inside defer blocks" unless allow_return
454
+
455
+ value = nil
456
+ prepared_setup = []
457
+ prepared_value = statement.value
458
+ prepared_cleanups = []
459
+ if statement.value
460
+ prepared_setup, prepared_value, prepared_cleanups = prepare_expression_with_cleanups(
461
+ statement.value,
462
+ env: local_env,
463
+ expected_type: return_type,
464
+ allow_root_statement_foreign: true,
465
+ materialize_array_calls: !array_type?(return_type),
466
+ )
467
+ lowered.concat(prepared_setup)
468
+ end
469
+ if prepared_value && (foreign_call = foreign_call_info(prepared_value, local_env))
470
+ setup, value = lower_foreign_call_statement(foreign_call, env: local_env, expected_type: return_type, statement_position: false)
471
+ lowered.concat(setup)
472
+ end
473
+ value ||= prepared_value ? lower_contextual_expression(
474
+ prepared_value,
475
+ env: local_env,
476
+ expected_type: return_type,
477
+ contextual_int_to_float: contextual_int_to_float_target?(return_type),
478
+ ) : nil
479
+ if prepared_cleanups.any? && cstr_trackable_type?(return_type)
480
+ raise LoweringError, "formatted string temporaries cannot be returned as borrowed text; use std.fmt.format(f\"...\") when ownership must escape"
481
+ end
482
+
483
+ prepared_cleanup_list = prepared_cleanups.flat_map(&:itself)
484
+ if prepared_cleanup_list.any? && return_type.is_a?(Types::Struct) && struct_contains_string_field?(return_type)
485
+ 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" }
486
+ end
487
+ cleanup = prepared_cleanup_list + cleanup_statements(local_defers, active_defers)
488
+ needs_proc_retain = value && contains_proc_storage_type?(return_type) && !local_defers.empty? && !expression_contains_proc_expr?(prepared_value)
489
+ if value && (!cleanup.empty? && !cleanup_safe_return_expression?(prepared_value) || needs_proc_retain)
490
+ return_value_name = fresh_c_temp_name(local_env, "return_value")
491
+ lowered << IR::LocalDecl.new(name: return_value_name, linkage_name: return_value_name, type: return_type, value:)
492
+ value = IR::Name.new(name: return_value_name, type: return_type, pointer: false)
493
+ end
494
+ lowered.concat(lower_proc_contained_retain_statements(value, return_type)) if needs_proc_retain
495
+ lowered.concat(cleanup)
496
+ lowered << IR::ReturnStmt.new(value:, line: statement.line, source_path: @ctx.current_analysis_path)
497
+ end
498
+
499
+ def lower_block_assignment_stmt(statement, lowered:, local_defers:, local_env:)
500
+ if statement.operator == "=" &&
501
+ statement.target.is_a?(AST::IndexAccess) &&
502
+ statement.target.index.is_a?(AST::RangeExpr) &&
503
+ statement.value.is_a?(AST::ExpressionList)
504
+ lowered.concat(lower_range_index_assignment(statement, env: local_env))
505
+ return
506
+ end
507
+ target = lower_assignment_target(statement.target, env: local_env)
508
+ prepared_cleanups = []
509
+ prepared_setup, prepared_value, prepared_cleanups = prepare_expression_with_cleanups(
510
+ statement.value,
511
+ env: local_env,
512
+ expected_type: target.type,
513
+ allow_root_statement_foreign: true,
514
+ materialize_array_calls: !array_type?(target.type),
515
+ )
516
+ lowered.concat(prepared_setup)
517
+ if (foreign_call = foreign_call_info(prepared_value, local_env))
518
+ setup, value, call_type, release_assignments, cleanup_statements = lower_foreign_call_components(
519
+ foreign_call,
520
+ env: local_env,
521
+ expected_type: target.type,
522
+ statement_position: false,
523
+ )
524
+ lowered.concat(setup)
525
+ raise LoweringError, "foreign call used in assignment must return a value" if call_type == @ctx.types.fetch("void")
526
+ raise LoweringError, "consuming foreign calls must return void" unless release_assignments.empty?
527
+
528
+ lowered << IR::Assignment.new(target:, operator: statement.operator, value:)
529
+ lowered.concat(cleanup_statements)
530
+ update_cstr_metadata_for_assignment!(statement, prepared_value, local_env)
531
+ local_defers.concat(suppress_format_releases_for_assignment(prepared_cleanups, target.type))
532
+ return
533
+ else
534
+ value = if statement.operator == "="
535
+ lower_contextual_expression(
536
+ prepared_value,
537
+ env: local_env,
538
+ expected_type: target.type,
539
+ external_numeric: external_numeric_assignment_target?(statement.target, env: local_env),
540
+ contextual_int_to_float: contextual_int_to_float_target?(target.type),
541
+ )
542
+ elsif ["+=", "-=", "*=", "/="].include?(statement.operator)
543
+ lower_contextual_expression(
544
+ prepared_value,
545
+ env: local_env,
546
+ expected_type: target.type,
547
+ contextual_int_to_float: contextual_int_to_float_target?(target.type),
548
+ )
549
+ else
550
+ lower_expression(prepared_value, env: local_env, expected_type: target.type)
551
+ end
552
+ end
553
+ if target.type.is_a?(Types::Nullable) && value && !value.type.is_a?(Types::Nullable) && !pointer_like_type?(target.type.base)
554
+ value = nullable_some_literal(target.type, value)
555
+ end
556
+ update_cstr_metadata_for_assignment!(statement, prepared_value, local_env)
557
+ local_defers.concat(suppress_format_releases_for_assignment(prepared_cleanups, target.type))
558
+ operator = statement.operator
559
+ if ["+=", "-=", "*=", "/="].include?(operator) &&
560
+ (target.type.is_a?(Types::Vector) || target.type.is_a?(Types::Matrix) || target.type.is_a?(Types::Quaternion))
561
+ binary_op = operator[0...-1]
562
+ expanded = lower_vector_binary_op(binary_op, target, target.type, value, value.type, target.type)
563
+ value = expanded || IR::Binary.new(operator: binary_op, left: target, right: value, type: target.type)
564
+ operator = "="
565
+ end
566
+ if operator == "=" && contains_proc_storage_type?(target.type)
567
+ rhs_name = fresh_c_temp_name(local_env, "proc_assign")
568
+ lowered << IR::LocalDecl.new(name: rhs_name, linkage_name: rhs_name, type: target.type, value:)
569
+ rhs = IR::Name.new(name: rhs_name, type: target.type, pointer: false)
570
+ lowered.concat(lower_proc_selective_retain_statements(rhs, statement.value, target.type))
571
+ lowered.concat(lower_proc_contained_guarded_release_statements(target, target.type))
572
+ lowered << IR::Assignment.new(target:, operator: "=", value: rhs)
573
+ else
574
+ lowered << IR::Assignment.new(target:, operator:, value:)
575
+ end
576
+ end
577
+
578
+ def lower_block_if_stmt(statement, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
579
+ if statement.inline
580
+ lowered.concat(lower_inline_if_stmt(statement, env: local_env, active_defers:, return_type:, allow_return:))
581
+ return
582
+ end
583
+
584
+ false_refinements = {}
585
+ branch_entries = []
586
+
587
+ unless statement.branches.empty?
588
+ if @bypass_sema_type_cache
589
+ ct_cond = compile_time_const_value(statement.branches.first.condition, env: local_env)
590
+ if ct_cond == true
591
+ lowered.concat(lower_block(
592
+ statement.branches.first.body,
593
+ env: local_env,
594
+ active_defers: active_defers + local_defers,
595
+ return_type:,
596
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
597
+ allow_return:,
598
+ ))
599
+ return
600
+ elsif ct_cond == false
601
+ if statement.else_body
602
+ lowered.concat(lower_block(
603
+ statement.else_body,
604
+ env: local_env,
605
+ active_defers: active_defers + local_defers,
606
+ return_type:,
607
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
608
+ allow_return:,
609
+ ))
610
+ end
611
+ return
612
+ end
613
+ end
614
+ end
615
+
616
+ statement.branches.each do |branch|
617
+ branch_env = env_with_refinements(local_env, false_refinements)
618
+ condition_setup, prepared_condition, condition_cleanups = prepare_expression_with_cleanups(
619
+ branch.condition,
620
+ env: branch_env,
621
+ expected_type: @ctx.types.fetch("bool"),
622
+ )
623
+ true_refinements = merge_refinements(false_refinements, flow_refinements(branch.condition, truthy: true, env: branch_env))
624
+
625
+ branch_entries << [
626
+ condition_setup,
627
+ condition_cleanups,
628
+ lower_expression(prepared_condition, env: branch_env, expected_type: @ctx.types.fetch("bool")),
629
+ lower_block(
630
+ branch.body,
631
+ env: env_with_refinements(local_env, true_refinements),
632
+ active_defers: active_defers + local_defers,
633
+ return_type:,
634
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
635
+ allow_return:,
636
+ ),
637
+ ]
638
+
639
+ false_refinements = merge_refinements(false_refinements, flow_refinements(branch.condition, truthy: false, env: branch_env))
640
+ end
641
+
642
+ nested_else_body = statement.else_body ? lower_block(
643
+ statement.else_body,
644
+ env: env_with_refinements(local_env, false_refinements),
645
+ active_defers: active_defers + local_defers,
646
+ return_type:,
647
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
648
+ allow_return:,
649
+ ) : []
650
+
651
+ nested_if = nested_else_body
652
+ branch_entries.reverse_each do |condition_setup, condition_cleanups, condition, then_body|
653
+ condition_cleanup_statements = condition_cleanups.flat_map(&:itself)
654
+ nested_if = [
655
+ *condition_setup,
656
+ IR::IfStmt.new(
657
+ condition:,
658
+ then_body: condition_cleanup_statements + then_body,
659
+ else_body: condition_cleanup_statements + nested_if,
660
+ ),
661
+ ]
662
+ end
663
+ lowered.concat(nested_if)
664
+
665
+ merge_cstr_metadata_after_if_statement!(statement, local_env)
666
+
667
+ if statement.else_body.nil? && statement.branches.all? { |branch| cfg_block_always_terminates?(branch.body) }
668
+ local_env[:scopes] = scopes_with_refinements(local_env[:scopes], false_refinements)
669
+ end
670
+ end
671
+
672
+ def lower_block_local_decl_stmt(statement, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
673
+ if statement.destructure_bindings
674
+ lower_destructure_decl(statement, env: local_env, lowered:, local_defers:, active_defers:, return_type:, loop_flow:, allow_return:)
675
+ return
676
+ end
677
+
678
+ storage_type = if statement.else_body
679
+ infer_expression_type(statement.value, env: local_env)
680
+ elsif statement.type
681
+ resolve_type_ref(statement.type)
682
+ else
683
+ infer_expression_type(statement.value, env: local_env)
684
+ end
685
+ type = if statement.else_body
686
+ statement.type ? resolve_type_ref(statement.type) : let_else_success_type(storage_type)
687
+ else
688
+ storage_type
689
+ end
690
+ linkage_name = let_else_storage_c_name(statement, local_env)
691
+ decl_name = bind_let_else_local?(statement) ? statement.name : linkage_name
692
+ prepared_setup = []
693
+ prepared_value = statement.value
694
+ prepared_cleanups = []
695
+ emitted_decl = false
696
+ if statement.value
697
+ local_env[:current_local_name] = linkage_name
698
+ prepared_setup, prepared_value, prepared_cleanups = prepare_expression_with_cleanups(
699
+ statement.value,
700
+ env: local_env,
701
+ expected_type: storage_type,
702
+ allow_root_statement_foreign: true,
703
+ materialize_array_calls: !array_type?(storage_type),
704
+ )
705
+ lowered.concat(prepared_setup)
706
+ end
707
+ if prepared_value && (foreign_call = foreign_call_info(prepared_value, local_env))
708
+ setup, value, call_type, release_assignments, cleanup_statements = lower_foreign_call_components(
709
+ foreign_call,
710
+ env: local_env,
711
+ expected_type: storage_type,
712
+ statement_position: false,
713
+ )
714
+ lowered.concat(setup)
715
+ raise LoweringError, "foreign call used to initialize #{statement.name} must return a value" if call_type == @ctx.types.fetch("void")
716
+ raise LoweringError, "consuming foreign calls must return void" unless release_assignments.empty?
717
+
718
+ lowered << IR::LocalDecl.new(name: decl_name, linkage_name:, type: storage_type, value:, line: statement.line, source_path: @ctx.current_analysis_path)
719
+ lowered.concat(cleanup_statements)
720
+ emitted_decl = true
721
+ elsif prepared_value.is_a?(AST::ProcExpr)
722
+ setup, value = lower_proc_expression_for_local(prepared_value, env: local_env, local_name: statement.name, proc_type: storage_type)
723
+ lowered.concat(setup)
724
+ elsif prepared_value
725
+ value = lower_contextual_expression(
726
+ prepared_value,
727
+ env: local_env,
728
+ expected_type: storage_type,
729
+ contextual_int_to_float: statement.type && contextual_int_to_float_target?(type),
730
+ )
731
+ else
732
+ value = IR::ZeroInit.new(type: storage_type)
733
+ end
734
+ if value && storage_type.is_a?(Types::Nullable) && !value.type.is_a?(Types::Nullable) && !pointer_like_type?(storage_type.base)
735
+ value = nullable_some_literal(storage_type, value)
736
+ end
737
+ if bind_let_else_local?(statement)
738
+ current_actual_scope(local_env[:scopes])[statement.name] = local_binding(
739
+ type:,
740
+ storage_type:,
741
+ linkage_name:,
742
+ mutable: statement.kind == :var,
743
+ pointer: false,
744
+ projection: statement.else_body ? let_else_binding_projection(storage_type) : nil,
745
+ cstr_backed: cstr_backed_storage_value?(storage_type, prepared_value, local_env),
746
+ cstr_list_backed: cstr_list_backed_storage_value?(storage_type, prepared_value, local_env),
747
+ const_value: statement.else_body ? nil : statement.kind == :let && prepared_value ? compile_time_const_value(prepared_value, env: local_env) : nil,
748
+ )
749
+ end
750
+ 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
751
+ if statement.else_body
752
+ else_env = if statement.else_binding
753
+ duplicate_env(local_env).tap do |env_with_error|
754
+ current_actual_scope(env_with_error[:scopes])[statement.else_binding.name] = local_binding(
755
+ type: let_else_error_type(storage_type),
756
+ storage_type:,
757
+ linkage_name:,
758
+ mutable: false,
759
+ pointer: false,
760
+ projection: :result_failure_error,
761
+ )
762
+ end
763
+ else
764
+ local_env
765
+ end
766
+ else_body = lower_block(
767
+ statement.else_body,
768
+ env: else_env,
769
+ active_defers: active_defers + local_defers + prepared_cleanups,
770
+ return_type:,
771
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
772
+ allow_return:,
773
+ )
774
+ local_ref = IR::Name.new(name: linkage_name, type: storage_type, pointer: false)
775
+ lowered << IR::IfStmt.new(
776
+ condition: let_else_failure_condition(local_ref, storage_type),
777
+ then_body: else_body,
778
+ else_body: nil,
779
+ )
780
+ end
781
+ local_defers.concat(prepared_cleanups)
782
+ if contains_proc_storage_type?(storage_type)
783
+ local_value = IR::Name.new(name: linkage_name, type: storage_type, pointer: false)
784
+ local_defers << lower_proc_contained_guarded_release_statements(local_value, storage_type)
785
+ if statement.value && !expression_contains_proc_expr?(statement.value)
786
+ lowered.concat(lower_proc_contained_retain_statements(local_value, storage_type))
787
+ end
788
+ end
789
+ end
790
+
791
+ def lower_block_match_stmt(statement, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
792
+ if statement.inline
793
+ lowered.concat(lower_inline_match_stmt(statement, env: local_env, active_defers:, return_type:, allow_return:))
794
+ return
795
+ end
796
+ scrutinee_type = infer_expression_type(statement.expression, env: local_env)
797
+ expression_setup, prepared_expression, expression_cleanups = prepare_expression_with_cleanups(
798
+ statement.expression,
799
+ env: local_env,
800
+ expected_type: scrutinee_type,
801
+ )
802
+ lowered.concat(expression_setup)
803
+ expression = lower_expression(prepared_expression, env: local_env, expected_type: scrutinee_type)
804
+
805
+ if scrutinee_type.is_a?(Types::Variant) &&
806
+ statement.arms.any? { |arm| arm.binding_name && !wildcard_arm_pattern?(arm.pattern) } &&
807
+ !duplicable_foreign_argument_expression?(expression)
808
+ scrutinee_linkage_name = fresh_c_temp_name(local_env, "match_value")
809
+ lowered << IR::LocalDecl.new(name: scrutinee_linkage_name, linkage_name: scrutinee_linkage_name, type: scrutinee_type, value: expression)
810
+ expression = IR::Name.new(name: scrutinee_linkage_name, type: scrutinee_type, pointer: false)
811
+ end
812
+
813
+ if scrutinee_type.is_a?(Types::Variant)
814
+ if statement.arms.any? { |arm| arm.pattern.is_a?(AST::Call) }
815
+ lower_block_variant_struct_pattern_match(statement, scrutinee_type:, expression:, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
816
+ else
817
+ kind_type = @ctx.types.fetch("int")
818
+ kind_expr = IR::Member.new(receiver: expression, member: "kind", type: kind_type)
819
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
820
+ cases = statement.arms.map do |arm|
821
+ arm_local_env = duplicate_env(local_env)
822
+ binding_decl = if arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
823
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
824
+ if arm_name && scrutinee_type.has_payload?(arm_name)
825
+ fields = scrutinee_type.arm(arm_name)
826
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
827
+ data_expr = IR::Member.new(receiver: expression, member: "data", type: nil)
828
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
829
+ binding_c = c_local_name(arm.binding_name)
830
+ arm_local_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: true, pointer: false)
831
+ IR::LocalDecl.new(name: arm.binding_name, linkage_name: binding_c, type: payload_type, value: arm_expr)
832
+ end
833
+ end
834
+ body = lower_block(
835
+ arm.body,
836
+ env: arm_local_env,
837
+ active_defers: active_defers + local_defers,
838
+ return_type:,
839
+ loop_flow: arm_loop_flow,
840
+ allow_return:,
841
+ )
842
+ body = [binding_decl, *body].compact if binding_decl
843
+ if wildcard_arm_pattern?(arm.pattern)
844
+ IR::SwitchDefaultCase.new(body:)
845
+ else
846
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
847
+ IR::SwitchCase.new(value: IR::Name.new(name: enum_member_c_name(scrutinee_type, "kind_#{arm_name}"), type: kind_type, pointer: false), body:)
848
+ end
849
+ end
850
+ lowered << IR::SwitchStmt.new(expression: kind_expr, cases:, exhaustive: true)
851
+ end
852
+ elsif scrutinee_type.is_a?(Types::StringView)
853
+ lower_block_string_match_stmt(statement, scrutinee_type:, expression:, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
854
+ elsif scrutinee_type.is_a?(Types::Tuple)
855
+ lower_block_tuple_match_stmt(statement, scrutinee_type:, expression:, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
856
+ else
857
+ has_range_arms = statement.arms.any? { |arm| arm.pattern.is_a?(AST::RangeExpr) }
858
+
859
+ if has_range_arms
860
+ lower_block_range_match_stmt(statement, scrutinee_type:, expression:, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
861
+ else
862
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
863
+ cases = statement.arms.map do |arm|
864
+ body = lower_block(
865
+ arm.body,
866
+ env: local_env,
867
+ active_defers: active_defers + local_defers,
868
+ return_type:,
869
+ loop_flow: arm_loop_flow,
870
+ allow_return:,
871
+ )
872
+ if wildcard_arm_pattern?(arm.pattern)
873
+ IR::SwitchDefaultCase.new(body:)
874
+ else
875
+ value = lower_expression(arm.pattern, env: local_env, expected_type: scrutinee_type)
876
+ IR::SwitchCase.new(value:, body:)
877
+ end
878
+ end
879
+ lowered << IR::SwitchStmt.new(expression:, cases:, exhaustive: true)
880
+ end
881
+ end
882
+ lowered.concat(expression_cleanups.flat_map(&:itself))
883
+ end
884
+
885
+ def lower_block_variant_struct_pattern_match(statement, scrutinee_type:, expression:, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
886
+ kind_type = @ctx.types.fetch("int")
887
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
888
+ @match_label_counter ||= 0
889
+ @match_label_counter += 1
890
+ m = @match_label_counter
891
+ match_end_label = "__mt_match_#{m}_end"
892
+ arm_next_label = "__mt_match_#{m}_arm_next"
893
+
894
+ statement.arms.each_with_index do |arm, arm_index|
895
+ arm_local_env = duplicate_env(local_env)
896
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern) unless wildcard_arm_pattern?(arm.pattern)
897
+
898
+ if arm_index > 0
899
+ lowered << IR::LabelStmt.new(name: "__mt_match_#{m}_arm_#{arm_index}")
900
+ end
901
+
902
+ if arm_name && !wildcard_arm_pattern?(arm.pattern)
903
+ tag_value = IR::Name.new(name: enum_member_c_name(scrutinee_type, "kind_#{arm_name}"), type: kind_type, pointer: false)
904
+ tag_expr = IR::Member.new(receiver: expression, member: "kind", type: kind_type)
905
+ goto_label = if arm_index < statement.arms.length - 1
906
+ "__mt_match_#{m}_arm_#{arm_index + 1}"
907
+ else
908
+ arm_next_label
909
+ end
910
+ tag_check = IR::Binary.new(operator: "!=", left: tag_expr, right: tag_value, type: @ctx.types.fetch("bool"))
911
+ lowered << IR::IfStmt.new(condition: tag_check, then_body: [IR::GotoStmt.new(label: goto_label)], else_body: [])
912
+ end
913
+
914
+ if arm_name && !wildcard_arm_pattern?(arm.pattern) && scrutinee_type.has_payload?(arm_name)
915
+ fields = scrutinee_type.arm(arm_name)
916
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
917
+ data_expr = IR::Member.new(receiver: expression, member: "data", type: nil)
918
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
919
+ payload_c_name = fresh_c_temp_name(arm_local_env, "match_payload")
920
+ arm_local_env[:scopes].last["__mt_payload"] = local_binding(type: payload_type, linkage_name: payload_c_name, mutable: true, pointer: false)
921
+
922
+ arm_body_ir = []
923
+ arm_body_ir << IR::LocalDecl.new(name: payload_c_name, linkage_name: payload_c_name, type: payload_type, value: arm_expr)
924
+
925
+ nested_struct_fields = nil
926
+ nested_struct_c_name = nil
927
+ nested_struct_type = nil
928
+ if fields.size == 1 && fields.values.first.is_a?(Types::Struct)
929
+ nested_struct_type = fields.values.first
930
+ nested_struct_fields = nested_struct_type.fields
931
+ single_field_name = fields.keys.first
932
+ nested_struct_c_name = fresh_c_temp_name(arm_local_env, "match_struct")
933
+ struct_expr = IR::Member.new(
934
+ receiver: IR::Name.new(name: payload_c_name, type: payload_type, pointer: false),
935
+ member: single_field_name,
936
+ type: nested_struct_type,
937
+ )
938
+ arm_body_ir << IR::LocalDecl.new(name: nested_struct_c_name, linkage_name: nested_struct_c_name, type: nested_struct_type, value: struct_expr)
939
+ end
940
+
941
+ if arm.pattern.is_a?(AST::Call) && !arm.pattern.arguments.empty?
942
+ pattern_fields = nested_struct_fields || fields
943
+ pattern_receiver_name = nested_struct_c_name || payload_c_name
944
+ pattern_receiver_type = nested_struct_type || payload_type
945
+
946
+ arm.pattern.arguments.each do |arg|
947
+ if !arg.name && arg.value.is_a?(AST::BinaryOp) && arg.value.left.is_a?(AST::Identifier)
948
+ field_name = arg.value.left.name
949
+ next unless pattern_fields.key?(field_name)
950
+
951
+ field_type = pattern_fields[field_name]
952
+ 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)
953
+ field_expr = dereference_if_self_ref(field_expr, field_type, scrutinee_type)
954
+ rhs_expr = lower_expression(arg.value.right, env: arm_local_env, expected_type: field_type)
955
+ guard_condition = IR::Binary.new(operator: arg.value.operator, left: field_expr, right: rhs_expr, type: @ctx.types.fetch("bool"))
956
+ arm_body_ir << IR::IfStmt.new(condition: guard_condition, then_body: [], else_body: [IR::GotoStmt.new(label: goto_label)])
957
+ end
958
+
959
+ if arg.name
960
+ field_name = arg.name
961
+ next unless pattern_fields.key?(field_name)
962
+
963
+ field_type = pattern_fields[field_name]
964
+ 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)
965
+ field_expr = dereference_if_self_ref(field_expr, field_type, scrutinee_type)
966
+ value_expr = lower_expression(arg.value, env: arm_local_env, expected_type: field_type)
967
+ eq_check = IR::Binary.new(operator: "!=", left: field_expr, right: value_expr, type: @ctx.types.fetch("bool"))
968
+ arm_body_ir << IR::IfStmt.new(condition: eq_check, then_body: [IR::GotoStmt.new(label: goto_label)], else_body: [])
969
+ end
970
+ end
971
+
972
+ arm.pattern.arguments.each do |arg|
973
+ next if arg.name
974
+ next unless arg.value.is_a?(AST::Identifier)
975
+ next if arg.value.name == "_"
976
+
977
+ field_name = arg.value.name
978
+ next unless pattern_fields.key?(field_name)
979
+
980
+ field_type = pattern_fields[field_name]
981
+ binding_c = c_local_name(field_name)
982
+ 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)
983
+ field_expr = dereference_if_self_ref(field_expr, field_type, scrutinee_type)
984
+ arm_body_ir << IR::LocalDecl.new(name: binding_c, linkage_name: binding_c, type: field_type, value: field_expr)
985
+ arm_local_env[:scopes].last[field_name] = local_binding(type: field_type, linkage_name: binding_c, mutable: false, pointer: false)
986
+ end
987
+ end
988
+
989
+ if arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
990
+ binding_c = c_local_name(arm.binding_name)
991
+ if arm_name && scrutinee_type.has_payload?(arm_name)
992
+ payload_key = "__mt_payload"
993
+ if arm_local_env[:scopes].last.key?(payload_key)
994
+ payload_binding = arm_local_env[:scopes].last[payload_key]
995
+ arm_local_env[:scopes].last[arm.binding_name] = local_binding(type: payload_binding[:type], linkage_name: binding_c, mutable: true, pointer: false)
996
+ payload_ref = IR::Name.new(name: payload_binding[:linkage_name], type: payload_binding[:type], pointer: false)
997
+ arm_body_ir << IR::LocalDecl.new(name: arm.binding_name, linkage_name: binding_c, type: payload_binding[:type], value: payload_ref)
998
+ end
999
+ end
1000
+ end
1001
+
1002
+ body = lower_block(
1003
+ arm.body,
1004
+ env: arm_local_env,
1005
+ active_defers: active_defers + local_defers,
1006
+ return_type:,
1007
+ loop_flow: arm_loop_flow,
1008
+ allow_return:,
1009
+ )
1010
+ arm_body_ir.concat(body)
1011
+ arm_body_ir << IR::GotoStmt.new(label: match_end_label)
1012
+
1013
+ lowered << IR::BlockStmt.new(body: arm_body_ir)
1014
+ else
1015
+ body = lower_block(
1016
+ arm.body,
1017
+ env: arm_local_env,
1018
+ active_defers: active_defers + local_defers,
1019
+ return_type:,
1020
+ loop_flow: arm_loop_flow,
1021
+ allow_return:,
1022
+ )
1023
+ lowered << IR::BlockStmt.new(body: body + [IR::GotoStmt.new(label: match_end_label)])
1024
+ end
1025
+ end
1026
+
1027
+ lowered << IR::LabelStmt.new(name: arm_next_label)
1028
+ lowered << IR::LabelStmt.new(name: match_end_label)
1029
+ end
1030
+
1031
+ def lower_block_string_match_stmt(statement, scrutinee_type:, expression:, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
1032
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
1033
+ bool_type = @ctx.types.fetch("bool")
1034
+ non_wildcard = statement.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1035
+ wildcard = statement.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
1036
+ else_body = if wildcard
1037
+ lower_block(
1038
+ wildcard.body,
1039
+ env: local_env,
1040
+ active_defers: active_defers + local_defers,
1041
+ return_type:,
1042
+ loop_flow: arm_loop_flow,
1043
+ allow_return:,
1044
+ )
1045
+ else
1046
+ []
1047
+ end
1048
+ non_wildcard.reverse_each do |arm|
1049
+ arm_body = lower_block(
1050
+ arm.body,
1051
+ env: local_env,
1052
+ active_defers: active_defers + local_defers,
1053
+ return_type:,
1054
+ loop_flow: arm_loop_flow,
1055
+ allow_return:,
1056
+ )
1057
+ lit = lower_expression(arm.pattern, env: local_env, expected_type: scrutinee_type)
1058
+ cond = IR::Binary.new(operator: "==", left: expression, right: lit, type: bool_type)
1059
+ else_body = [IR::IfStmt.new(condition: cond, then_body: arm_body, else_body:)]
1060
+ end
1061
+ lowered.concat(else_body)
1062
+ end
1063
+
1064
+ def lower_block_tuple_match_stmt(statement, scrutinee_type:, expression:, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
1065
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
1066
+ bool_type = @ctx.types.fetch("bool")
1067
+ non_wildcard = statement.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1068
+ wildcard = statement.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
1069
+ else_body = if wildcard
1070
+ lower_block(
1071
+ wildcard.body,
1072
+ env: local_env,
1073
+ active_defers: active_defers + local_defers,
1074
+ return_type:,
1075
+ loop_flow: arm_loop_flow,
1076
+ allow_return:,
1077
+ )
1078
+ else
1079
+ []
1080
+ end
1081
+ non_wildcard.reverse_each do |arm|
1082
+ arm_body = lower_block(
1083
+ arm.body,
1084
+ env: local_env,
1085
+ active_defers: active_defers + local_defers,
1086
+ return_type:,
1087
+ loop_flow: arm_loop_flow,
1088
+ allow_return:,
1089
+ )
1090
+ cond = tuple_arm_condition(arm, expression, scrutinee_type, bool_type, env: local_env)
1091
+ else_body = [IR::IfStmt.new(condition: cond, then_body: arm_body, else_body:)]
1092
+ end
1093
+ lowered.concat(else_body)
1094
+ end
1095
+
1096
+ def lower_block_range_match_stmt(statement, scrutinee_type:, expression:, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
1097
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
1098
+ bool_type = @ctx.types.fetch("bool")
1099
+ non_wildcard = statement.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1100
+ wildcard = statement.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
1101
+ else_body = if wildcard
1102
+ lower_block(wildcard.body, env: local_env, active_defers: active_defers + local_defers, return_type:, loop_flow: arm_loop_flow, allow_return:)
1103
+ else
1104
+ []
1105
+ end
1106
+ non_wildcard.reverse_each do |arm|
1107
+ arm_body = lower_block(
1108
+ arm.body,
1109
+ env: local_env,
1110
+ active_defers: active_defers + local_defers,
1111
+ return_type:,
1112
+ loop_flow: arm_loop_flow,
1113
+ allow_return:,
1114
+ )
1115
+ cond = if arm.pattern.is_a?(AST::RangeExpr)
1116
+ lower_range_match_condition(arm.pattern, expression, bool_type, env: local_env)
1117
+ else
1118
+ lit = lower_expression(arm.pattern, env: local_env, expected_type: scrutinee_type)
1119
+ IR::Binary.new(operator: "==", left: expression, right: lit, type: bool_type)
1120
+ end
1121
+ else_body = [IR::IfStmt.new(condition: cond, then_body: arm_body, else_body:)]
1122
+ end
1123
+ lowered.concat(else_body)
1124
+ end
1091
1125
  end
1092
1126
  end