mt-lang 0.3.13 → 0.3.16

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/docs/index.html +31 -23
  4. data/docs/language-design.md +13 -17
  5. data/docs/language-manual.md +19 -7
  6. data/lib/milk_tea/base.rb +1 -1
  7. data/lib/milk_tea/core/ast.rb +2 -2
  8. data/lib/milk_tea/core/control_flow/builder.rb +62 -2
  9. data/lib/milk_tea/core/lowering/async/analysis.rb +3 -9
  10. data/lib/milk_tea/core/lowering/async/lowering.rb +2 -10
  11. data/lib/milk_tea/core/lowering/async/normalization.rb +2 -7
  12. data/lib/milk_tea/core/lowering/block.rb +1 -5
  13. data/lib/milk_tea/core/lowering/expressions.rb +47 -3
  14. data/lib/milk_tea/core/lowering/proc.rb +1 -5
  15. data/lib/milk_tea/core/lowering/resolve.rb +26 -2
  16. data/lib/milk_tea/core/lowering/scans.rb +3 -1
  17. data/lib/milk_tea/core/lowering/utils.rb +7 -28
  18. data/lib/milk_tea/core/parser/statements.rb +18 -12
  19. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +3 -11
  20. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +2 -3
  21. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +1 -1
  22. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +1 -2
  23. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +2 -4
  24. data/lib/milk_tea/core/semantic_analyzer/statements.rb +2 -8
  25. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +29 -2
  26. data/lib/milk_tea/core/types.rb +0 -6
  27. data/lib/milk_tea/tooling/linter/imports_platform.rb +2 -4
  28. data/lib/milk_tea/tooling/linter/release_rules.rb +1 -6
  29. data/lib/milk_tea/tooling/linter/source_helpers.rb +0 -6
  30. data/lib/milk_tea/tooling/linter/visitors.rb +4 -8
  31. data/lib/milk_tea/tooling/sexpr_dumper.rb +2 -0
  32. data/std/asset_pack.mt +1 -1
  33. data/std/async/libuv_runtime.mt +6 -6
  34. data/std/async/mailbox.mt +4 -4
  35. data/std/{cell.mt → box.mt} +9 -9
  36. data/std/cookie.mt +2 -2
  37. data/std/crypto.mt +1 -1
  38. data/std/curl/runtime.mt +10 -10
  39. data/std/fs.linux.mt +26 -26
  40. data/std/fs.windows.mt +22 -22
  41. data/std/goap.mt +2 -2
  42. data/std/http/server.mt +21 -21
  43. data/std/http.mt +14 -14
  44. data/std/jobs.mt +4 -4
  45. data/std/json.mt +5 -5
  46. data/std/log.mt +1 -1
  47. data/std/mem/tracking.mt +1 -1
  48. data/std/net/channel.mt +4 -4
  49. data/std/net/clock.mt +6 -6
  50. data/std/net/discovery.mt +7 -7
  51. data/std/net/lobby.mt +10 -10
  52. data/std/net/manager.mt +1 -1
  53. data/std/net/mux.mt +4 -4
  54. data/std/net/nat.mt +4 -4
  55. data/std/net/packet.mt +3 -3
  56. data/std/net/punch.mt +3 -3
  57. data/std/net/rpc.mt +3 -3
  58. data/std/net/session.mt +15 -15
  59. data/std/net/stun.mt +4 -4
  60. data/std/net/turn.mt +4 -4
  61. data/std/oauth2.mt +12 -12
  62. data/std/path.mt +8 -8
  63. data/std/process.mt +4 -4
  64. data/std/raylib/packed_assets.mt +5 -5
  65. data/std/serialize.mt +1 -1
  66. data/std/tar.mt +10 -10
  67. data/std/terminal.mt +18 -18
  68. data/std/tls.mt +8 -8
  69. data/std/uri.mt +1 -1
  70. data/std/utility.mt +1 -1
  71. metadata +3 -3
@@ -1506,10 +1506,54 @@ module MilkTea
1506
1506
  return false unless receiver_type.is_a?(Types::VariantArmPayload)
1507
1507
 
1508
1508
  outer = receiver_type.variant_type
1509
- return field_type == outer if outer.is_a?(Types::Variant)
1510
- return field_type == outer if outer.is_a?(Types::VariantInstance)
1509
+ return false unless outer.is_a?(Types::Variant) || outer.is_a?(Types::VariantInstance)
1511
1510
 
1512
- false
1511
+ # The C backend pointer-breaks every cyclic field, including cycles that
1512
+ # run through generic instantiations such as `Option[Atom]` inside `Atom`.
1513
+ # Deref the access whenever the field type can reach the outer variant.
1514
+ type_reaches_target?(field_type, outer)
1515
+ end
1516
+
1517
+ def type_reaches_target?(type, target)
1518
+ type_reaches_target_with_visited?(type, target, {})
1519
+ end
1520
+
1521
+ def type_reaches_target_with_visited?(type, target, visited)
1522
+ return true if type == target
1523
+ return false if visited[type.object_id]
1524
+
1525
+ visited[type.object_id] = true
1526
+ reachability_children(type).any? do |child|
1527
+ type_reaches_target_with_visited?(child, target, visited)
1528
+ end
1529
+ end
1530
+
1531
+ # Structural expansion used by cyclic-field reachability. Mirrors the
1532
+ # CBackend's `aggregate_type_dependencies` so the deref injected for a
1533
+ # pointer-broken cyclic field agrees with the emitted C struct layout.
1534
+ def reachability_children(type)
1535
+ case type
1536
+ when Types::Nullable
1537
+ [type.base]
1538
+ when Types::GenericInstance
1539
+ if pointer_type?(type)
1540
+ []
1541
+ elsif array_type?(type)
1542
+ [array_element_type(type)]
1543
+ else
1544
+ []
1545
+ end
1546
+ when Types::Span
1547
+ [type.element_type]
1548
+ when Types::Struct, Types::StructInstance, Types::Union
1549
+ type.fields.values
1550
+ when Types::Variant, Types::VariantInstance
1551
+ type.arm_names.flat_map { |name| type.arm(name).values }
1552
+ when Types::VariantArmPayload
1553
+ type.fields.values
1554
+ else
1555
+ []
1556
+ end
1513
1557
  end
1514
1558
 
1515
1559
  def lower_compile_time_handle_member(handle, member, type)
@@ -180,11 +180,7 @@ module MilkTea
180
180
  when AST::ReturnStmt
181
181
  collect_proc_captures_from_expression(statement.value, env, local_scopes, captures) if statement.value
182
182
  when AST::DeferStmt
183
- if statement.body
184
- collect_proc_captures_from_statements(statement.body, env, local_scopes + [{}], captures)
185
- else
186
- collect_proc_captures_from_expression(statement.expression, env, local_scopes, captures)
187
- end
183
+ collect_proc_captures_from_statements(statement.body, env, local_scopes + [{}], captures)
188
184
  when AST::ExpressionStmt
189
185
  collect_proc_captures_from_expression(statement.expression, env, local_scopes, captures)
190
186
  when AST::BreakStmt, AST::ContinueStmt, AST::PassStmt
@@ -1137,9 +1137,33 @@ module MilkTea
1137
1137
  return unless left_type.is_a?(Types::Primitive) && right_type.is_a?(Types::Primitive)
1138
1138
  return unless left_type.integer? && right_type.integer?
1139
1139
  return unless left_type.fixed_width_integer? && right_type.fixed_width_integer?
1140
- return unless left_type.signed_integer? == right_type.signed_integer?
1141
1140
 
1142
- left_type.integer_width >= right_type.integer_width ? left_type : right_type
1141
+ # Mirrors the semantic analyzer's rule: same signedness picks the wider
1142
+ # type; mixed signed/unsigned promotes to the narrowest signed type that
1143
+ # holds both operands' full ranges (a strictly-wider signed type covers
1144
+ # an unsigned operand, otherwise widen to the next signed width). Mixing
1145
+ # with a 64-bit unsigned type has no safe signed common type.
1146
+ if left_type.signed_integer? == right_type.signed_integer?
1147
+ return left_type.integer_width >= right_type.integer_width ? left_type : right_type
1148
+ end
1149
+
1150
+ signed_type, unsigned_type = if left_type.signed_integer?
1151
+ [left_type, right_type]
1152
+ else
1153
+ [right_type, left_type]
1154
+ end
1155
+
1156
+ return signed_type if signed_type.integer_width > unsigned_type.integer_width
1157
+
1158
+ signed_type_above_width(unsigned_type.integer_width)
1159
+ end
1160
+
1161
+ def signed_type_above_width(width)
1162
+ case width
1163
+ when 8 then @ctx.types.fetch("short")
1164
+ when 16 then @ctx.types.fetch("int")
1165
+ when 32 then @ctx.types.fetch("long")
1166
+ end
1143
1167
  end
1144
1168
 
1145
1169
  def wider_float_type(left_type, right_type)
@@ -144,8 +144,10 @@ module MilkTea
144
144
  (expression && expression_uses_pattern?(expression, &predicate)) || block_uses_expression_pattern?(statement.body, &predicate)
145
145
  when AST::ReturnStmt
146
146
  statement.value && expression_uses_pattern?(statement.value, &predicate)
147
- when AST::DeferStmt, AST::ExpressionStmt
147
+ when AST::ExpressionStmt
148
148
  expression_uses_pattern?(statement.expression, &predicate)
149
+ when AST::DeferStmt
150
+ block_uses_expression_pattern?(statement.body, &predicate)
149
151
  else
150
152
  false
151
153
  end
@@ -760,32 +760,6 @@ module MilkTea
760
760
  end
761
761
  end
762
762
 
763
- def lower_defer_cleanup_expression(expression, env:)
764
- prepared_setup, prepared_expression, prepared_cleanups = prepare_expression_with_cleanups(
765
- expression,
766
- env:,
767
- expected_type: infer_expression_type(expression, env:),
768
- allow_root_statement_foreign: true,
769
- )
770
-
771
- lowered = []
772
- lowered.concat(prepared_setup)
773
- if (foreign_call = foreign_call_info(prepared_expression, env))
774
- setup, = lower_foreign_call_statement(
775
- foreign_call,
776
- env:,
777
- expected_type: foreign_call[:binding].type.return_type,
778
- statement_position: true,
779
- discard_result: true,
780
- )
781
- lowered.concat(setup)
782
- else
783
- lowered << IR::ExpressionStmt.new(expression: lower_expression(prepared_expression, env:))
784
- end
785
- lowered.concat(prepared_cleanups.flat_map(&:itself))
786
- lowered
787
- end
788
-
789
763
  def lower_defer_cleanup_body(statements, env:, return_type:)
790
764
  lower_block(statements, env:, active_defers: [], return_type:, loop_flow: nil, allow_return: false)
791
765
  end
@@ -943,7 +917,12 @@ module MilkTea
943
917
  payload_type = Types::VariantArmPayload.new(storage_type, arm_name, storage_type.arm(arm_name))
944
918
  data_expr = IR::Member.new(receiver: storage_expr, member: "data", type: nil)
945
919
  arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
946
- IR::Member.new(receiver: arm_expr, member: field_name, type: field_type)
920
+ member_expr = IR::Member.new(receiver: arm_expr, member: field_name, type: field_type)
921
+ if type_reaches_target?(field_type, payload_type.variant_type)
922
+ return IR::Unary.new(operator: "*", operand: member_expr, type: field_type)
923
+ end
924
+
925
+ member_expr
947
926
  end
948
927
 
949
928
  def infer_result_propagation_type(expression, env:)
@@ -1156,7 +1135,7 @@ module MilkTea
1156
1135
  cached = cache[text]
1157
1136
  return cached if cached
1158
1137
 
1159
- identifier = text.gsub(/[^A-Za-z0-9_]+/, "_").gsub(/_+/, "_").sub(/_+$/, "").sub(/^_{2,}/, "_")
1138
+ identifier = text.gsub(/[^A-Za-z0-9_]+/, "_").gsub(/_+/, "_").sub(/_+$/, "").sub(/^_+/, "")
1160
1139
  cache[text] = identifier.empty? ? "value" : identifier
1161
1140
  end
1162
1141
 
@@ -279,6 +279,18 @@ module MilkTea
279
279
  check(:colon) && @current + 1 < @tokens.length && @tokens[@current + 1].type != :newline
280
280
  end
281
281
 
282
+ def parse_inline_or_block_body(message)
283
+ if inline_block_body?
284
+ consume(:colon, "expected ':' #{message}")
285
+ @in_inline_block_body = true
286
+ result = [parse_statement]
287
+ @in_inline_block_body = false
288
+ result
289
+ else
290
+ parse_block
291
+ end
292
+ end
293
+
282
294
  def parse_match_stmt
283
295
  token = previous
284
296
  line = token.line
@@ -456,7 +468,7 @@ module MilkTea
456
468
  consume(:in, "expected 'in' in for loop")
457
469
  iterables = [parse_expression]
458
470
  iterables << parse_expression while match(:comma)
459
- body = parse_block
471
+ body = parse_inline_or_block_body("after 'in' iterables")
460
472
  AST::ForStmt.new(bindings:, iterables:, body:, line:, column: bindings.first.column)
461
473
  rescue ParseError => e
462
474
  raise unless @recovery_errors
@@ -530,7 +542,7 @@ module MilkTea
530
542
  token = previous
531
543
  line = token.line
532
544
  condition = parse_expression
533
- body = parse_block
545
+ body = parse_inline_or_block_body("after while condition")
534
546
  AST::WhileStmt.new(condition:, body:, line:, column: token.column, length: token.lexeme.length)
535
547
  rescue ParseError => e
536
548
  raise unless @recovery_errors
@@ -580,14 +592,8 @@ module MilkTea
580
592
  def parse_defer_stmt
581
593
  token = previous
582
594
  line = token.line
583
- if check(:colon)
584
- body = parse_block
585
- AST::DeferStmt.new(expression: nil, body:, line:, column: token.column, length: token.lexeme.length)
586
- else
587
- expression = parse_expression
588
- consume_end_of_statement unless block_expression?(expression)
589
- AST::DeferStmt.new(expression:, body: nil, line:, column: token.column, length: token.lexeme.length)
590
- end
595
+ body = parse_inline_or_block_body("after defer")
596
+ AST::DeferStmt.new(body:, line:, column: token.column, length: token.lexeme.length)
591
597
  end
592
598
 
593
599
  def parse_when_decl
@@ -690,7 +696,7 @@ module MilkTea
690
696
  consume(:in, "expected 'in' in for loop")
691
697
  iterables = [parse_expression]
692
698
  iterables << parse_expression while match(:comma)
693
- body = parse_block
699
+ body = parse_inline_or_block_body("after 'in' iterables")
694
700
  AST::ForStmt.new(bindings:, iterables:, body:, inline: true, line:, column: bindings.first.column)
695
701
  rescue ParseError => e
696
702
  raise unless @recovery_errors
@@ -705,7 +711,7 @@ module MilkTea
705
711
  def parse_inline_while_stmt(inline_token)
706
712
  line = inline_token.line
707
713
  condition = parse_expression
708
- body = parse_block
714
+ body = parse_inline_or_block_body("after inline while condition")
709
715
  AST::WhileStmt.new(condition:, body:, inline: true, line:, column: inline_token.column, length: inline_token.lexeme.length)
710
716
  rescue ParseError => e
711
717
  raise unless @recovery_errors
@@ -521,13 +521,9 @@ module MilkTea
521
521
  when AST::ReturnStmt
522
522
  line(statement.value ? "return #{render_expression(statement.value)}" : "return")
523
523
  when AST::DeferStmt
524
- if statement.body
525
- line("defer:")
526
- with_indent do
527
- statement.body.each { |nested| emit_statement(nested) }
528
- end
529
- else
530
- line("defer #{render_expression(statement.expression)}")
524
+ line("defer:")
525
+ with_indent do
526
+ statement.body.each { |nested| emit_statement(nested) }
531
527
  end
532
528
  when AST::ExpressionStmt
533
529
  line(render_expression(statement.expression))
@@ -649,10 +645,6 @@ module MilkTea
649
645
  "continue"
650
646
  when AST::ReturnStmt
651
647
  statement.value ? "return #{render_expression(statement.value)}" : "return"
652
- when AST::DeferStmt
653
- return nil if statement.body
654
-
655
- "defer #{render_expression(statement.expression)}"
656
648
  when AST::ExpressionStmt
657
649
  render_expression(statement.expression)
658
650
  else
@@ -180,8 +180,7 @@ module MilkTea
180
180
  when AST::UnsafeStmt
181
181
  statement.body.each { |s| validate_async_statement!(s) }
182
182
  when AST::DeferStmt
183
- validate_async_expression_support!(statement.expression, context: "defer cleanup") if statement.expression
184
- statement.body&.each { |s| validate_async_statement!(s) }
183
+ statement.body.each { |s| validate_async_statement!(s) }
185
184
  when AST::WhenStmt
186
185
  statement.branches.each { |branch| branch.body.each { |s| validate_async_statement!(s) } }
187
186
  statement.else_body&.each { |s| validate_async_statement!(s) }
@@ -230,7 +229,7 @@ module MilkTea
230
229
  when AST::ReturnStmt
231
230
  statement.value && expression_contains_await?(statement.value)
232
231
  when AST::DeferStmt
233
- (statement.expression && expression_contains_await?(statement.expression)) || (statement.body && statements_contain_await?(statement.body))
232
+ statements_contain_await?(statement.body)
234
233
  when AST::ExpressionStmt
235
234
  expression_contains_await?(statement.expression)
236
235
  else
@@ -209,7 +209,7 @@ module MilkTea
209
209
  end
210
210
  end
211
211
  when AST::DeferStmt
212
- lines.concat(statement_list_lines(statement.body)) if statement.body
212
+ lines.concat(statement_list_lines(statement.body))
213
213
  when AST::Assignment
214
214
  lines << expression_end_line(statement.value)
215
215
  when AST::ReturnStmt
@@ -375,9 +375,8 @@ module MilkTea
375
375
  body_names = names.dup
376
376
  stmt.body&.each { |s| check_stmt_names(s, scopes, binding, body_names) }
377
377
  when AST::DeferStmt
378
- check_expr_names(stmt.expression, scopes, binding, names) if stmt.expression
379
378
  body_names = names.dup
380
- stmt.body&.each { |s| check_stmt_names(s, scopes, binding, body_names) }
379
+ stmt.body.each { |s| check_stmt_names(s, scopes, binding, body_names) }
381
380
  when AST::WhenStmt
382
381
  check_expr_names(stmt.discriminant, scopes, binding, names)
383
382
  stmt.branches.each do |b|
@@ -154,8 +154,7 @@ module MilkTea
154
154
  statement.iterables.each { |iterable| preassign_local_binding_ids_in_expression(iterable) }
155
155
  preassign_local_binding_ids_in_statements(statement.body || [])
156
156
  when AST::DeferStmt
157
- preassign_local_binding_ids_in_expression(statement.expression) if statement.expression
158
- preassign_local_binding_ids_in_statements(statement.body || []) if statement.body
157
+ preassign_local_binding_ids_in_statements(statement.body)
159
158
  when AST::ReturnStmt
160
159
  preassign_local_binding_ids_in_expression(statement.value) if statement.value
161
160
  when AST::StaticAssert
@@ -319,8 +318,7 @@ module MilkTea
319
318
  end
320
319
  walk_statements_for_precheck_resolution(statement.body || [], for_scopes, declaration_ids, identifier_ids)
321
320
  when AST::DeferStmt
322
- walk_expression_for_precheck_resolution(statement.expression, block_scopes, identifier_ids, declaration_ids) if statement.expression
323
- walk_statements_for_precheck_resolution(statement.body || [], block_scopes, declaration_ids, identifier_ids) if statement.body
321
+ walk_statements_for_precheck_resolution(statement.body, block_scopes, declaration_ids, identifier_ids)
324
322
  when AST::ExpressionStmt
325
323
  walk_expression_for_precheck_resolution(statement.expression, block_scopes, identifier_ids, declaration_ids)
326
324
  when AST::ReturnStmt
@@ -193,14 +193,8 @@ module MilkTea
193
193
  line: statement.line,
194
194
  )
195
195
  when AST::DeferStmt
196
- if statement.body
197
- with_loop_barrier do
198
- check_block(statement.body, scopes:, return_type:, allow_return: false)
199
- end
200
- else
201
- validate_consuming_foreign_expression!(statement.expression, scopes:, root_allowed: true)
202
- validate_hoistable_foreign_expression!(statement.expression, scopes:, root_hoistable: false)
203
- infer_expression(statement.expression, scopes:)
196
+ with_loop_barrier do
197
+ check_block(statement.body, scopes:, return_type:, allow_return: false)
204
198
  end
205
199
  when AST::ExpressionStmt
206
200
  validate_consuming_foreign_expression!(statement.expression, scopes:, root_allowed: true)
@@ -187,9 +187,36 @@ module MilkTea
187
187
  return unless left_type.integer? && right_type.integer?
188
188
  return left_type if left_type == right_type
189
189
  return unless left_type.fixed_width_integer? && right_type.fixed_width_integer?
190
- return unless left_type.signed_integer? == right_type.signed_integer?
191
190
 
192
- left_type.integer_width >= right_type.integer_width ? left_type : right_type
191
+ # Same signedness: the wider type wins (both operands are losslessly
192
+ # assignable to it).
193
+ if left_type.signed_integer? == right_type.signed_integer?
194
+ return left_type.integer_width >= right_type.integer_width ? left_type : right_type
195
+ end
196
+
197
+ # Mixed signed/unsigned: promote to the narrowest signed type that holds
198
+ # both operands' full ranges, mirroring the lossless assignment rule. A
199
+ # strictly-wider signed type covers an unsigned operand; equal-width or
200
+ # wider unsigned operands widen to the next signed width. Mixing with a
201
+ # 64-bit unsigned type has no safe signed common type, so callers fall
202
+ # back to requiring an explicit cast.
203
+ signed_type, unsigned_type = if left_type.signed_integer?
204
+ [left_type, right_type]
205
+ else
206
+ [right_type, left_type]
207
+ end
208
+
209
+ return signed_type if signed_type.integer_width > unsigned_type.integer_width
210
+
211
+ signed_type_above_width(unsigned_type.integer_width)
212
+ end
213
+
214
+ def signed_type_above_width(width)
215
+ case width
216
+ when 8 then @ctx.types.fetch("short")
217
+ when 16 then @ctx.types.fetch("int")
218
+ when 32 then @ctx.types.fetch("long")
219
+ end
193
220
  end
194
221
 
195
222
  def wider_float_type(left_type, right_type)
@@ -850,9 +850,6 @@ module MilkTea
850
850
  end
851
851
 
852
852
  def field_c_name(name)
853
- stripped = name.delete_suffix("_")
854
- return stripped if stripped != name && MilkTea::KEYWORDS.key?(stripped)
855
-
856
853
  name
857
854
  end
858
855
 
@@ -961,9 +958,6 @@ module MilkTea
961
958
  end
962
959
 
963
960
  def field_c_name(name)
964
- stripped = name.delete_suffix("_")
965
- return stripped if stripped != name && MilkTea::KEYWORDS.key?(stripped)
966
-
967
961
  name
968
962
  end
969
963
 
@@ -484,8 +484,7 @@ module MilkTea
484
484
  when AST::UnsafeStmt
485
485
  stmt.body.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) }
486
486
  when AST::DeferStmt
487
- collect_indirect_import_uses_from_expr(stmt.expression, used, binding_resolution, import_module_names) if stmt.expression
488
- stmt.body&.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) }
487
+ stmt.body.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) }
489
488
  when AST::ReturnStmt
490
489
  collect_indirect_import_uses_from_expr(stmt.value, used, binding_resolution, import_module_names) if stmt.value
491
490
  when AST::ExpressionStmt
@@ -766,8 +765,7 @@ module MilkTea
766
765
  when AST::UnsafeStmt
767
766
  stmt.body.each { |s| collect_names_from_statement(s, used) }
768
767
  when AST::DeferStmt
769
- collect_names_from_expr(stmt.expression, used) if stmt.expression
770
- stmt.body&.each { |s| collect_names_from_statement(s, used) }
768
+ stmt.body.each { |s| collect_names_from_statement(s, used) }
771
769
  when AST::ReturnStmt
772
770
  collect_names_from_expr(stmt.value, used) if stmt.value
773
771
  when AST::ExpressionStmt
@@ -169,9 +169,6 @@ module MilkTea
169
169
  when AST::UnsafeStmt
170
170
  _collect_own_released_names(stmt.body, result)
171
171
  when AST::DeferStmt
172
- if stmt.expression
173
- _collect_heap_release_names(stmt.expression, result)
174
- end
175
172
  _collect_own_released_names(stmt.body, result) if stmt.body.is_a?(Array)
176
173
  end
177
174
  end
@@ -476,7 +473,6 @@ module MilkTea
476
473
  when AST::UnsafeStmt
477
474
  _collect_released_names(stmt.body, result)
478
475
  when AST::DeferStmt
479
- _collect_released_names_in_expr(stmt.expression, result) if stmt.expression
480
476
  _collect_released_names(stmt.body, result) if stmt.body.is_a?(Array)
481
477
  end
482
478
  end
@@ -611,9 +607,8 @@ module MilkTea
611
607
  def release_call_in_stmt?(stmt, name)
612
608
  return false unless stmt
613
609
 
614
- # DeferStmt: inline form `defer x.release()` or block form `defer: …`
610
+ # DeferStmt: block form `defer: …` (inline defers are a single-statement body)
615
611
  if stmt.is_a?(AST::DeferStmt)
616
- return true if stmt.expression && _expr_has_release?(stmt.expression, name)
617
612
  return stmt.body.is_a?(Array) && stmt.body.any? { |s| release_call_in_stmt?(s, name) }
618
613
  end
619
614
 
@@ -240,8 +240,6 @@ module MilkTea
240
240
  expression_column(statement.expression)
241
241
  when AST::ReturnStmt
242
242
  expression_column(statement.value)
243
- when AST::DeferStmt
244
- expression_column(statement.expression)
245
243
  when AST::ExpressionStmt
246
244
  expression_column(statement.expression) || source_statement_span(statement.line)&.first
247
245
  else
@@ -267,8 +265,6 @@ module MilkTea
267
265
  expression_length(statement.expression)
268
266
  when AST::ReturnStmt
269
267
  expression_length(statement.value)
270
- when AST::DeferStmt
271
- expression_length(statement.expression)
272
268
  when AST::ExpressionStmt
273
269
  expression_length(statement.expression) || source_statement_span(statement.line)&.last
274
270
  else
@@ -326,8 +322,6 @@ module MilkTea
326
322
  walk_expression_tree(statement.expression, &block)
327
323
  when AST::ReturnStmt
328
324
  walk_expression_tree(statement.value, &block)
329
- when AST::DeferStmt
330
- walk_expression_tree(statement.expression, &block)
331
325
  when AST::ExpressionStmt
332
326
  walk_expression_tree(statement.expression, &block)
333
327
  when AST::StaticAssert
@@ -225,8 +225,7 @@ module MilkTea
225
225
  visit_expression(statement.value) if statement.value
226
226
  flag_redundant_widening_cast(statement.value) if statement.value
227
227
  when AST::DeferStmt
228
- visit_expression(statement.expression) if statement.expression
229
- with_scope { visit_statement_list(statement.body) } if statement.body
228
+ with_scope { visit_statement_list(statement.body) }
230
229
  when AST::ExpressionStmt
231
230
  visit_expression(statement.expression)
232
231
  check_useless_expression(statement)
@@ -1252,8 +1251,7 @@ module MilkTea
1252
1251
  when AST::UnsafeStmt
1253
1252
  stmt.body.each { |s| collect_borrows_from_stmt(s, names) }
1254
1253
  when AST::DeferStmt
1255
- collect_borrows_from_expr(stmt.expression, names) if stmt.expression
1256
- stmt.body&.each { |s| collect_borrows_from_stmt(s, names) }
1254
+ stmt.body.each { |s| collect_borrows_from_stmt(s, names) }
1257
1255
  when AST::WhenStmt
1258
1256
  collect_borrows_from_expr(stmt.discriminant, names)
1259
1257
  stmt.branches.each { |b| b.body.each { |s| collect_borrows_from_stmt(s, names) } }
@@ -1319,7 +1317,7 @@ module MilkTea
1319
1317
  when AST::UnsafeStmt
1320
1318
  stmt.body.each { |s| collect_writes_from_stmt(s, names) }
1321
1319
  when AST::DeferStmt
1322
- stmt.body&.each { |s| collect_writes_from_stmt(s, names) }
1320
+ stmt.body.each { |s| collect_writes_from_stmt(s, names) }
1323
1321
  end
1324
1322
  end
1325
1323
 
@@ -1376,9 +1374,7 @@ module MilkTea
1376
1374
  stmt.body.each { |s| location = find_borrow_location_in_stmt(s, name); return location if location }
1377
1375
  nil
1378
1376
  when AST::DeferStmt
1379
- location = find_borrow_location_in_expr(stmt.expression, name) if stmt.expression
1380
- return location if location
1381
- stmt.body&.each { |s| location = find_borrow_location_in_stmt(s, name); return location if location }
1377
+ stmt.body.each { |s| location = find_borrow_location_in_stmt(s, name); return location if location }
1382
1378
  nil
1383
1379
  when AST::WhenStmt
1384
1380
  location = find_borrow_location_in_expr(stmt.discriminant, name)
@@ -38,6 +38,7 @@ module MilkTea
38
38
  when Float then buf << format_float(value)
39
39
  when String then emit_string(value, buf)
40
40
  when Symbol then buf << ":" << value.to_s.tr("_", "-")
41
+ when Hash, Set then nil
41
42
  else buf << escape_string(value.to_s)
42
43
  end
43
44
  end
@@ -50,6 +51,7 @@ module MilkTea
50
51
  buf << "(" << type_name
51
52
  members.each do |field|
52
53
  val = node.public_send(field)
54
+ next if val.is_a?(Hash) || val.is_a?(Set)
53
55
  buf << " :" << field.to_s << " "
54
56
  emit_value(val, buf)
55
57
  end
data/std/asset_pack.mt CHANGED
@@ -187,7 +187,7 @@ function read_entry_metadata(file: stdio.File?) -> Result[EntryMetadata, Error]:
187
187
 
188
188
  function read_path_matches(file: stdio.File?, path_length: ptr_uint, logical_path: str) -> Result[bool, Error]:
189
189
  let path_buffer = heap.must_alloc[ubyte](path_length)
190
- defer heap.release(path_buffer)
190
+ defer: heap.release(path_buffer)
191
191
 
192
192
  if not read_exact(file, path_buffer, path_length):
193
193
  return Result[bool, Error].failure(error= Error.malformed_index)
@@ -475,7 +475,7 @@ public function result[T](task: Task[T]) -> T:
475
475
  if not completed[T](task):
476
476
  fatal(c"async.result called before task completed")
477
477
 
478
- defer task.release(task.frame)
478
+ defer: task.release(task.frame)
479
479
  return task.take_result(task.frame)
480
480
 
481
481
 
@@ -484,7 +484,7 @@ public function wait_on[T](runtime: Runtime, task: Task[T]) -> T:
484
484
  while not task.ready(task.frame):
485
485
  runtime_poll(runtime)
486
486
 
487
- defer task.release(task.frame)
487
+ defer: task.release(task.frame)
488
488
  return task.take_result(task.frame)
489
489
 
490
490
 
@@ -503,7 +503,7 @@ public function wait[T](root: proc() -> Task[T]) -> T:
503
503
 
504
504
  var runtime = runtime_create()
505
505
  runtime_activate(runtime)
506
- defer runtime_release(ref_of(runtime))
506
+ defer: runtime_release(ref_of(runtime))
507
507
  return wait_on[T](runtime, root())
508
508
 
509
509
 
@@ -514,7 +514,7 @@ public function run(root: proc() -> Task[void]) -> void:
514
514
 
515
515
  var runtime = runtime_create()
516
516
  runtime_activate(runtime)
517
- defer runtime_release(ref_of(runtime))
517
+ defer: runtime_release(ref_of(runtime))
518
518
  run_on(runtime, root())
519
519
 
520
520
 
@@ -524,7 +524,7 @@ public function with_runtime[T](body: proc(runtime: Runtime) -> T) -> T:
524
524
 
525
525
  var runtime = runtime_create()
526
526
  runtime_activate(runtime)
527
- defer runtime_release(ref_of(runtime))
527
+ defer: runtime_release(ref_of(runtime))
528
528
  return body(runtime)
529
529
 
530
530
 
@@ -535,5 +535,5 @@ public function run_with_runtime(body: proc(runtime: Runtime) -> void) -> void:
535
535
 
536
536
  var runtime = runtime_create()
537
537
  runtime_activate(runtime)
538
- defer runtime_release(ref_of(runtime))
538
+ defer: runtime_release(ref_of(runtime))
539
539
  body(runtime)
data/std/async/mailbox.mt CHANGED
@@ -138,7 +138,7 @@ extending Mailbox[T]:
138
138
 
139
139
  var mutex = unsafe: read(state).mutex
140
140
  mutex.lock()
141
- defer mutex.unlock()
141
+ defer: mutex.unlock()
142
142
 
143
143
  let handle = unsafe: read(state).handle else:
144
144
  return Result[bool, Error].failure(error = mailbox_error("mailbox is closed"))
@@ -161,7 +161,7 @@ extending Mailbox[T]:
161
161
 
162
162
  var mutex = unsafe: read(state).mutex
163
163
  mutex.lock()
164
- defer mutex.unlock()
164
+ defer: mutex.unlock()
165
165
  return unsafe: read(state).queue.pop_front()
166
166
 
167
167
 
@@ -171,7 +171,7 @@ extending Mailbox[T]:
171
171
 
172
172
  var mutex = unsafe: read(state).mutex
173
173
  mutex.lock()
174
- defer mutex.unlock()
174
+ defer: mutex.unlock()
175
175
 
176
176
  var drained = vec.Vec[T].with_capacity(unsafe: read(state).queue.len())
177
177
  while true:
@@ -187,5 +187,5 @@ extending Mailbox[T]:
187
187
 
188
188
  var mutex = unsafe: read(state).mutex
189
189
  mutex.lock()
190
- defer mutex.unlock()
190
+ defer: mutex.unlock()
191
191
  return unsafe: read(state).queue.is_empty()