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.
@@ -150,28 +150,16 @@ module MilkTea
150
150
  projection: :result_failure_error,
151
151
  )
152
152
  end
153
- else_body = if statements_contain_await?(statement.else_body, async_info)
154
- lower_async_cf_statements(
155
- statement.else_body,
156
- env: else_env,
157
- frame_expr:,
158
- raw_frame_expr:,
159
- resume_linkage_name:,
160
- async_info:,
161
- active_defers:,
162
- loop_flow:,
163
- )
164
- else
165
- lower_async_non_await_statements(
166
- statement.else_body,
167
- env: else_env,
168
- frame_expr:,
169
- raw_frame_expr:,
170
- async_info:,
171
- active_defers:,
172
- loop_flow:,
173
- )
174
- end
153
+ else_body = lower_async_body(
154
+ statement.else_body,
155
+ env: else_env,
156
+ frame_expr:,
157
+ raw_frame_expr:,
158
+ resume_linkage_name:,
159
+ async_info:,
160
+ active_defers:,
161
+ loop_flow:,
162
+ )
175
163
  lowered << IR::IfStmt.new(
176
164
  condition: let_else_failure_condition(target, storage_type),
177
165
  then_body: else_body,
@@ -214,6 +202,35 @@ module MilkTea
214
202
  end
215
203
  end
216
204
 
205
+ def lower_async_body(body, env:, frame_expr:, raw_frame_expr:, async_info:, resume_linkage_name: nil, active_defers: [], loop_flow: nil)
206
+ if statements_contain_await?(body, async_info)
207
+ lower_async_cf_statements(body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
208
+ else
209
+ lower_async_non_await_statements(body, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers:, loop_flow:)
210
+ end
211
+ end
212
+
213
+ def build_async_while_with_condition_setup(condition_setup, condition, body, break_label)
214
+ if condition_setup.empty?
215
+ stmts = [IR::WhileStmt.new(condition:, body:)]
216
+ stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(body, break_label)
217
+ return stmts
218
+ end
219
+
220
+ loop_body = [
221
+ *condition_setup,
222
+ IR::IfStmt.new(
223
+ condition: IR::Unary.new(operator: "not", operand: condition, type: @ctx.types.fetch("bool")),
224
+ then_body: [loop_exit_statement(loop_exit_break(break_label), local_defers: [], outer_defers: [])],
225
+ else_body: nil,
226
+ ),
227
+ *body,
228
+ ]
229
+ stmts = [IR::WhileStmt.new(condition: IR::BooleanLiteral.new(value: true, type: @ctx.types.fetch("bool")), body: loop_body)]
230
+ stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(loop_body, break_label)
231
+ stmts
232
+ end
233
+
217
234
  # Lower a list of statements that MAY contain await expressions inside nested control flow.
218
235
  # CPS-via-goto: labels placed inside if/while/match bodies, reachable from top-level switch dispatch.
219
236
  def lower_async_cf_statements(statements, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers: [], loop_flow: nil)
@@ -304,20 +321,12 @@ module MilkTea
304
321
  branch_entries = statement.branches.map do |branch|
305
322
  condition_setup, prepared_cond = prepare_expression_for_inline_lowering(branch.condition, env:)
306
323
  condition = lower_contextual_expression(prepared_cond, env:, expected_type: @ctx.types.fetch("bool"))
307
- body = if statements_contain_await?(branch.body, async_info)
308
- lower_async_cf_statements(branch.body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
309
- else
310
- lower_async_non_await_statements(branch.body, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers:, loop_flow:)
311
- end
324
+ body = lower_async_body(branch.body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
312
325
  { condition_setup:, condition:, body: }
313
326
  end
314
327
 
315
328
  else_body = if statement.else_body
316
- if statements_contain_await?(statement.else_body, async_info)
317
- lower_async_cf_statements(statement.else_body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
318
- else
319
- lower_async_non_await_statements(statement.else_body, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers:, loop_flow:)
320
- end
329
+ lower_async_body(statement.else_body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
321
330
  end
322
331
 
323
332
  nested_else = else_body
@@ -336,31 +345,10 @@ module MilkTea
336
345
  condition_setup, prepared_cond = prepare_expression_for_inline_lowering(statement.condition, env:)
337
346
  condition = lower_contextual_expression(prepared_cond, env:, expected_type: @ctx.types.fetch("bool"))
338
347
  inner_loop_flow = loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label))
339
- body = if statements_contain_await?(statement.body, async_info)
340
- lower_async_cf_statements(statement.body, env: duplicate_env(env), frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
341
- else
342
- lower_async_non_await_statements(statement.body, env: duplicate_env(env), frame_expr:, raw_frame_expr:, async_info:, active_defers:, loop_flow: inner_loop_flow)
343
- end
348
+ body = lower_async_body(statement.body, env: duplicate_env(env), frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
344
349
  body << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body, continue_label)
345
350
 
346
- if condition_setup.empty?
347
- stmts = [IR::WhileStmt.new(condition:, body:)]
348
- stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(body, break_label)
349
- return stmts
350
- end
351
-
352
- loop_body = [
353
- *condition_setup,
354
- IR::IfStmt.new(
355
- condition: IR::Unary.new(operator: "not", operand: condition, type: @ctx.types.fetch("bool")),
356
- then_body: [loop_exit_statement(loop_exit_break(break_label), local_defers: [], outer_defers: [])],
357
- else_body: nil,
358
- ),
359
- *body,
360
- ]
361
- stmts = [IR::WhileStmt.new(condition: IR::BooleanLiteral.new(value: true, type: @ctx.types.fetch("bool")), body: loop_body)]
362
- stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(loop_body, break_label)
363
- stmts
351
+ build_async_while_with_condition_setup(condition_setup, condition, body, break_label)
364
352
  end
365
353
 
366
354
  def lower_async_cf_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:)
@@ -405,11 +393,7 @@ module MilkTea
405
393
  )
406
394
  inner_loop_flow = loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label))
407
395
 
408
- body = if statements_contain_await?(statement.body, async_info)
409
- lower_async_cf_statements(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
410
- else
411
- lower_async_non_await_statements(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, async_info:, active_defers:, loop_flow: inner_loop_flow)
412
- end
396
+ body = lower_async_body(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
413
397
  body << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body, continue_label)
414
398
 
415
399
  cmp_op = inclusive ? "<=" : "<"
@@ -461,11 +445,7 @@ module MilkTea
461
445
  inner_loop_flow = loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label))
462
446
 
463
447
  assign_item = IR::Assignment.new(target: loop_var_expr, operator: "=", value: item_value)
464
- body_stmts = if statements_contain_await?(statement.body, async_info)
465
- lower_async_cf_statements(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
466
- else
467
- lower_async_non_await_statements(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, async_info:, active_defers:, loop_flow: inner_loop_flow)
468
- end
448
+ body_stmts = lower_async_body(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
469
449
  body_stmts << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body_stmts, continue_label)
470
450
 
471
451
  stmts = [
@@ -533,11 +513,7 @@ module MilkTea
533
513
  IR::Assignment.new(target: binding_target, operator: "=", value: loop_item_value)
534
514
  end
535
515
  inner_loop_flow = loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label))
536
- body_stmts = if statements_contain_await?(statement.body, async_info)
537
- lower_async_cf_statements(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
538
- else
539
- lower_async_non_await_statements(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, async_info:, active_defers:, loop_flow: inner_loop_flow)
540
- end
516
+ body_stmts = lower_async_body(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
541
517
  body_stmts << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body_stmts, continue_label)
542
518
 
543
519
  length_checks = iterable_entries.drop(1).each_with_index.map do |entry, offset|
@@ -593,11 +569,7 @@ module MilkTea
593
569
  kind_expr = IR::Member.new(receiver: match_expr, member: "kind", type: kind_type)
594
570
  cases = statement.arms.map do |arm|
595
571
  arm_env, binding_decl = async_variant_match_arm_binding(arm, match_expr, match_type, env:, frame_expr:, local_fields: async_info[:local_fields])
596
- arm_body = if statements_contain_await?(arm.body, async_info)
597
- lower_async_cf_statements(arm.body, env: arm_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: arm_loop_flow)
598
- else
599
- lower_async_non_await_statements(arm.body, env: arm_env, frame_expr:, raw_frame_expr:, async_info:, active_defers:, loop_flow: arm_loop_flow)
600
- end
572
+ arm_body = lower_async_body(arm.body, env: arm_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: arm_loop_flow)
601
573
  body = [binding_decl, *arm_body].compact + [IR::BreakStmt.new]
602
574
  if wildcard_arm_pattern?(arm.pattern)
603
575
  IR::SwitchDefaultCase.new(body: body)
@@ -611,11 +583,7 @@ module MilkTea
611
583
  end
612
584
 
613
585
  cases = statement.arms.map do |arm|
614
- arm_body = if statements_contain_await?(arm.body, async_info)
615
- lower_async_cf_statements(arm.body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: arm_loop_flow)
616
- else
617
- lower_async_non_await_statements(arm.body, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers:, loop_flow: arm_loop_flow)
618
- end
586
+ arm_body = lower_async_body(arm.body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: arm_loop_flow)
619
587
  if wildcard_arm_pattern?(arm.pattern)
620
588
  IR::SwitchDefaultCase.new(body: arm_body + [IR::BreakStmt.new])
621
589
  else
@@ -804,23 +772,7 @@ module MilkTea
804
772
  body << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body, continue_label)
805
773
  cond = lower_expression(prepared_cond, env:, expected_type: @ctx.types.fetch("bool"))
806
774
 
807
- if condition_setup.empty?
808
- stmts = [IR::WhileStmt.new(condition: cond, body:)]
809
- stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(body, break_label)
810
- return IR::BlockStmt.new(body: stmts)
811
- end
812
-
813
- loop_body = [
814
- *condition_setup,
815
- IR::IfStmt.new(
816
- condition: IR::Unary.new(operator: "not", operand: cond, type: @ctx.types.fetch("bool")),
817
- then_body: [loop_exit_statement(loop_exit_break(break_label), local_defers: [], outer_defers: [])],
818
- else_body: nil,
819
- ),
820
- *body,
821
- ]
822
- stmts = [IR::WhileStmt.new(condition: IR::BooleanLiteral.new(value: true, type: @ctx.types.fetch("bool")), body: loop_body)]
823
- stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(loop_body, break_label)
775
+ stmts = build_async_while_with_condition_setup(condition_setup, cond, body, break_label)
824
776
  IR::BlockStmt.new(body: stmts)
825
777
  end
826
778
 
@@ -1242,28 +1194,16 @@ module MilkTea
1242
1194
  projection: :result_failure_error,
1243
1195
  )
1244
1196
  end
1245
- else_body = if statements_contain_await?(statement.else_body, async_info)
1246
- lower_async_cf_statements(
1247
- statement.else_body,
1248
- env: else_env,
1249
- frame_expr:,
1250
- raw_frame_expr:,
1251
- resume_linkage_name:,
1252
- async_info:,
1253
- active_defers:,
1254
- loop_flow:,
1255
- )
1256
- else
1257
- lower_async_non_await_statements(
1258
- statement.else_body,
1259
- env: else_env,
1260
- frame_expr:,
1261
- raw_frame_expr:,
1262
- async_info:,
1263
- active_defers:,
1264
- loop_flow:,
1265
- )
1266
- end
1197
+ else_body = lower_async_body(
1198
+ statement.else_body,
1199
+ env: else_env,
1200
+ frame_expr:,
1201
+ raw_frame_expr:,
1202
+ resume_linkage_name:,
1203
+ async_info:,
1204
+ active_defers:,
1205
+ loop_flow:,
1206
+ )
1267
1207
  lowered << IR::IfStmt.new(
1268
1208
  condition: let_else_failure_condition(target, storage_type),
1269
1209
  then_body: else_body,
@@ -1315,28 +1255,16 @@ module MilkTea
1315
1255
  next [] if cleanup_entry[:body].empty?
1316
1256
 
1317
1257
  cleanup_env = duplicate_env(cleanup_entry[:env])
1318
- if statements_contain_await?(cleanup_entry[:body], async_info)
1319
- lower_async_cf_statements(
1320
- cleanup_entry[:body],
1321
- env: cleanup_env,
1322
- frame_expr:,
1323
- raw_frame_expr:,
1324
- resume_linkage_name: async_info.fetch(:resume_linkage_name),
1325
- async_info:,
1326
- active_defers: [],
1327
- loop_flow: nil,
1328
- )
1329
- else
1330
- lower_async_non_await_statements(
1331
- cleanup_entry[:body],
1332
- env: cleanup_env,
1333
- frame_expr:,
1334
- raw_frame_expr:,
1335
- async_info:,
1336
- active_defers: [],
1337
- loop_flow: nil,
1338
- )
1339
- end
1258
+ lower_async_body(
1259
+ cleanup_entry[:body],
1260
+ env: cleanup_env,
1261
+ frame_expr:,
1262
+ raw_frame_expr:,
1263
+ resume_linkage_name: async_info.fetch(:resume_linkage_name),
1264
+ async_info:,
1265
+ active_defers: [],
1266
+ loop_flow: nil,
1267
+ )
1340
1268
  end
1341
1269
  end
1342
1270
 
@@ -1403,5 +1331,83 @@ module MilkTea
1403
1331
  lowered << IR::ReturnStmt.new(value: nil)
1404
1332
  lowered
1405
1333
  end
1334
+
1335
+ def async_variant_match_arm_binding(arm, scrutinee_expr, scrutinee_type, env:, frame_expr: nil, local_fields: nil)
1336
+ arm_env = duplicate_env(env)
1337
+ binding_decl = nil
1338
+
1339
+ if arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
1340
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
1341
+ if arm_name && scrutinee_type.has_payload?(arm_name)
1342
+ fields = scrutinee_type.arm(arm_name)
1343
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
1344
+
1345
+ field_key = async_match_binding_field_key(arm)
1346
+ field_info = local_fields&.fetch(field_key, nil)
1347
+ if field_info && frame_expr
1348
+ target = async_frame_field_expression(frame_expr, field_info[:field_name], field_info[:storage_type])
1349
+ binding_c = async_frame_field_c_name(field_info[:field_name])
1350
+ arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: false, pointer: false)
1351
+ data_expr = IR::Member.new(receiver: scrutinee_expr, member: "data", type: nil)
1352
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
1353
+ binding_decl = IR::Assignment.new(target:, operator: "=", value: arm_expr)
1354
+ else
1355
+ data_expr = IR::Member.new(receiver: scrutinee_expr, member: "data", type: nil)
1356
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
1357
+ binding_c = c_local_name(arm.binding_name)
1358
+ arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: false, pointer: false)
1359
+ binding_decl = IR::LocalDecl.new(name: arm.binding_name, linkage_name: binding_c, type: payload_type, value: arm_expr)
1360
+ end
1361
+ end
1362
+ end
1363
+
1364
+ [arm_env, binding_decl]
1365
+ end
1366
+
1367
+ def bind_async_variant_match_arm_env!(arm_env, scrutinee_type, arm)
1368
+ return unless scrutinee_type.is_a?(Types::Variant)
1369
+ return unless arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
1370
+
1371
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
1372
+ return unless arm_name && scrutinee_type.has_payload?(arm_name)
1373
+
1374
+ fields = scrutinee_type.arm(arm_name)
1375
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
1376
+ arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: c_local_name(arm.binding_name), mutable: true, pointer: false)
1377
+ end
1378
+
1379
+ def lower_async_loop_exit(target, local_defers, outer_defers, frame_expr:, raw_frame_expr:, async_info:)
1380
+ cleanup = lower_async_cleanup_entries(local_defers, outer_defers, frame_expr:, raw_frame_expr:, async_info:)
1381
+ if cleanup.empty?
1382
+ [loop_exit_statement(target, local_defers:, outer_defers:)]
1383
+ else
1384
+ label = target[:label]
1385
+ raise LoweringError, "structured loop exits with cleanup are unsupported" unless label
1386
+
1387
+ cleanup + [IR::GotoStmt.new(label:)]
1388
+ end
1389
+ end
1390
+
1391
+ def async_local_decl_field_key(statement)
1392
+ return "__discard_#{statement.line}" if statement.name == "_"
1393
+
1394
+ statement.name
1395
+ end
1396
+
1397
+ def async_local_decl_field_name(statement)
1398
+ return "local_discard_#{statement.line}" if statement.name == "_"
1399
+
1400
+ "local_#{statement.name}"
1401
+ end
1402
+
1403
+ def async_match_binding_field_key(arm)
1404
+ @async_binding_counter ||= 0
1405
+ @async_binding_counter += 1
1406
+ "match_binding_#{@async_binding_counter}"
1407
+ end
1408
+
1409
+ def async_match_binding_field_name(arm)
1410
+ "local_match_binding_#{@async_binding_counter}"
1411
+ end
1406
1412
  end
1407
1413
  end