mt-lang 0.3.14 → 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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/docs/index.html +8 -8
  4. data/docs/language-design.md +4 -8
  5. data/docs/language-manual.md +17 -5
  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/proc.rb +1 -5
  14. data/lib/milk_tea/core/lowering/scans.rb +3 -1
  15. data/lib/milk_tea/core/lowering/utils.rb +1 -27
  16. data/lib/milk_tea/core/parser/statements.rb +18 -12
  17. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +3 -11
  18. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +2 -3
  19. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +1 -1
  20. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +1 -2
  21. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +2 -4
  22. data/lib/milk_tea/core/semantic_analyzer/statements.rb +2 -8
  23. data/lib/milk_tea/tooling/linter/imports_platform.rb +2 -4
  24. data/lib/milk_tea/tooling/linter/release_rules.rb +1 -6
  25. data/lib/milk_tea/tooling/linter/source_helpers.rb +0 -6
  26. data/lib/milk_tea/tooling/linter/visitors.rb +4 -8
  27. data/lib/milk_tea/tooling/sexpr_dumper.rb +2 -0
  28. data/std/asset_pack.mt +1 -1
  29. data/std/async/libuv_runtime.mt +6 -6
  30. data/std/async/mailbox.mt +4 -4
  31. data/std/cookie.mt +2 -2
  32. data/std/crypto.mt +1 -1
  33. data/std/curl/runtime.mt +10 -10
  34. data/std/fs.linux.mt +26 -26
  35. data/std/fs.windows.mt +22 -22
  36. data/std/goap.mt +2 -2
  37. data/std/http/server.mt +21 -21
  38. data/std/http.mt +14 -14
  39. data/std/jobs.mt +4 -4
  40. data/std/json.mt +5 -5
  41. data/std/log.mt +1 -1
  42. data/std/mem/tracking.mt +1 -1
  43. data/std/net/channel.mt +4 -4
  44. data/std/net/clock.mt +6 -6
  45. data/std/net/discovery.mt +7 -7
  46. data/std/net/lobby.mt +10 -10
  47. data/std/net/manager.mt +1 -1
  48. data/std/net/mux.mt +4 -4
  49. data/std/net/nat.mt +4 -4
  50. data/std/net/packet.mt +3 -3
  51. data/std/net/punch.mt +3 -3
  52. data/std/net/rpc.mt +3 -3
  53. data/std/net/session.mt +15 -15
  54. data/std/net/stun.mt +4 -4
  55. data/std/net/turn.mt +4 -4
  56. data/std/oauth2.mt +12 -12
  57. data/std/path.mt +8 -8
  58. data/std/process.mt +4 -4
  59. data/std/raylib/packed_assets.mt +5 -5
  60. data/std/serialize.mt +1 -1
  61. data/std/tar.mt +10 -10
  62. data/std/terminal.mt +18 -18
  63. data/std/tls.mt +8 -8
  64. data/std/uri.mt +1 -1
  65. data/std/utility.mt +1 -1
  66. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12f7a371c9cb4162851cefb9277292455904b6e0bedcba05312770f00d724481
4
- data.tar.gz: 4d73b7c4ef5c45abc731edbfd6e465067230b6772669ad8952a75247cd10e2ad
3
+ metadata.gz: b5cd3c1ef5e380b8d0c5d4e20f829400d01c6fdfd3e495e8c98050f4af8b59ba
4
+ data.tar.gz: 0e083d1780c9d591b996604a8b489726fa0f0c6fb85b8dc03da87b681383d5ca
5
5
  SHA512:
6
- metadata.gz: d434a523bf7e5678512af400a5054765d62976778d27c1d647203fa03a39145509648d82d32d56d02218da1324d66717b1eb1da75543ea513e49fcc2e9fc5410
7
- data.tar.gz: d2fa42eb284fd01df9492fa772d69c46853712862dd6f8d4f1f0f20aa8c40fc996abf821763175419cb58abb2ffe2da94a750e30b8c47d7b0c9ae9c31ec75f75
6
+ metadata.gz: 44f866e7d6bf6fc4998e0a9054bf898b27ed47ef38149de1881baf1d810e845b94e3d1f3ff304486654ea339329d726a10d969bcff4021943cc98b03bfe3fe01
7
+ data.tar.gz: 0fa7237c14aa9e038b4d51ae08a5971430d0e47b97fb19caf619aeb80a5e562de99aae1cf342a11ffea41e3b2268a128454bbf022b3e9182ddd94ff2ff0aff45
data/README.md CHANGED
@@ -477,7 +477,7 @@ Rules:
477
477
  - Conditions must be `bool`.
478
478
  - There is no truthy or falsy coercion from integers or pointers.
479
479
  - `pass` is an explicit no-op statement for intentionally empty block bodies.
480
- - `if` supports an inline single-statement form: `if cond: stmt else: stmt`. The body is a single statement on the same line instead of an indented block.
480
+ - Every block-taking statement accepts a `:`-newline-indented block, or a single statement on the same line after `:`. This applies to `if`/`else` (e.g. `if cond: stmt else: stmt`), `while`, `for`, `defer` (e.g. `defer: cleanup()`), and `unsafe`. `match` arms are the exception: their inline form is a single value expression.
481
481
 
482
482
  `match` supports the following scrutinee types:
483
483
 
@@ -615,7 +615,7 @@ Rules:
615
615
 
616
616
  `defer`:
617
617
 
618
- - `defer expr`
618
+ - `defer: expr` — single-statement inline form
619
619
  - `defer:` block form
620
620
  - `return` is not allowed inside defer blocks.
621
621
 
data/docs/index.html CHANGED
@@ -658,12 +658,12 @@ function main() -> int:
658
658
 
659
659
  ## Collections: Vec and Map (import std.hash for primitive keys)
660
660
  var entities = vec.Vec[Entity].create()
661
- defer entities.release()
661
+ defer: entities.release()
662
662
  entities.push(Entity.create(1, EntityKind.player, "Hero"))
663
663
  entities.push(Entity.create(2, EntityKind.enemy, "Goblin"))
664
664
 
665
665
  var name_counts = map.Map[str, int].create()
666
- defer name_counts.release()
666
+ defer: name_counts.release()
667
667
  name_counts.set("player", 1)
668
668
  name_counts.set("enemy", 5)
669
669
 
@@ -742,7 +742,7 @@ function main() -> int:
742
742
  io.print_line(f"Position: #{position.x:.2}, #{position.y:.2}")
743
743
  io.print_line(f"ID hex: #{player.entity.id:x}")
744
744
  var msg = fmt.format(f"Score: #{score}")
745
- defer msg.release()
745
+ defer: msg.release()
746
746
 
747
747
  ## Heredoc string
748
748
  let help = <<-HELP
@@ -753,7 +753,7 @@ function main() -> int:
753
753
 
754
754
  ## Defer: cleanup at scope exit (lowers to C cleanup labels)
755
755
  let data: own[int] = heap.must_alloc[int](1)
756
- defer heap.release[int](data)
756
+ defer: heap.release[int](data)
757
757
 
758
758
  ## Proc / closure
759
759
  let scale = make_scaler(2.5)
@@ -1598,11 +1598,11 @@ match record:
1598
1598
  <button class="copy-btn" onclick="copyCode(this)">Copy</button>
1599
1599
  <pre><code>function main() -> int:
1600
1600
  rl.init_window(800, 600, "Game")
1601
- defer rl.close_window()
1601
+ defer: rl.close_window()
1602
1602
 
1603
1603
  while not rl.window_should_close():
1604
1604
  rl.begin_drawing()
1605
- defer rl.end_drawing()
1605
+ defer: rl.end_drawing()
1606
1606
  render_frame()
1607
1607
 
1608
1608
  return 0</code></pre>
@@ -2149,8 +2149,8 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
2149
2149
  let closed_sub = window.closed.subscribe(on_close)?
2150
2150
  let resized_sub = window.resized.subscribe(on_resize)?
2151
2151
 
2152
- defer window.closed.unsubscribe(closed_sub)
2153
- defer window.resized.unsubscribe(resized_sub)
2152
+ defer: window.closed.unsubscribe(closed_sub)
2153
+ defer: window.resized.unsubscribe(resized_sub)
2154
2154
 
2155
2155
  return Result[void, EventError].success()</code></pre>
2156
2156
  </div>
@@ -119,7 +119,7 @@ extending Player:
119
119
 
120
120
  function main() -> int:
121
121
  rl.init_window(screen_width, screen_height, "Milk Tea")
122
- defer rl.close_window()
122
+ defer: rl.close_window()
123
123
 
124
124
  var player = Player(
125
125
  position = rl.Vector2(x = 400.0, y = 300.0),
@@ -132,7 +132,7 @@ function main() -> int:
132
132
  player.update(dt)
133
133
 
134
134
  rl.begin_drawing()
135
- defer rl.end_drawing()
135
+ defer: rl.end_drawing()
136
136
 
137
137
  rl.clear_background(rl.BLACK)
138
138
  rl.draw_circle_v(player.position, player.radius, rl.GOLD)
@@ -423,16 +423,12 @@ Milk Tea should include a small number of control-flow features that materially
423
423
 
424
424
  #### `defer`
425
425
 
426
- `defer` registers cleanup code at scope exit and lowers to obvious cleanup labels in C.
426
+ `defer` registers cleanup code at scope exit and lowers to obvious cleanup labels in C. Both forms use the block-header `:`:
427
427
 
428
428
  ```mt
429
-
430
-
431
429
  function load_texture(path: str) -> Result[Texture, LoadError]:
432
430
  let texture = rl.load_texture(path)
433
-
434
- if texture.id == 0:
435
- return Result[Texture, LoadError].failure(error= LoadError.file_not_found)
431
+ defer: rl.unload_texture(texture)
436
432
 
437
433
  return Result[Texture, LoadError].success(value= texture)
438
434
  ```
@@ -596,6 +596,8 @@ if x > 10: return "big" else if x > 5: return "med" else: return "small"
596
596
 
597
597
  The inline form supports all statement kinds in the body (`return`, `let`, `var`, `defer`, assignment, expression statement, etc.). Blocks and inline form may be mixed: any branch may use either form independently.
598
598
 
599
+ Every block-taking statement follows the same rule: a `:` followed by a newline starts an indented block, while a `:` followed by anything else on the same line introduces exactly one inline statement. This applies uniformly to `if`/`else`, `while`, `for`, `defer`, and `unsafe` (§4.3, §4.4, §4.5). `match` arms are the exception: their inline form is a single value expression (§4.2).
600
+
599
601
  ### 4.2 Match
600
602
 
601
603
  Scrutinee types supported:
@@ -717,6 +719,13 @@ Single-form `for` supports:
717
719
  - `span[T]`
718
720
  - custom structural iterables with a non-editable zero-argument `iter()` method
719
721
 
722
+ Every loop body accepts either a `:`-newline-indented block or a single statement on the same line as the header:
723
+
724
+ ```mt
725
+ while ready: poll()
726
+ for value in values: accumulate(value)
727
+ ```
728
+
720
729
  Iterator protocol for custom structural iterables:
721
730
 
722
731
  - the iterable value must expose `iter()` with no parameters
@@ -795,10 +804,10 @@ Rules:
795
804
 
796
805
  ### 4.4 Defer
797
806
 
798
- Forms:
807
+ Both forms use the block-header `:` introduced in §4:
799
808
 
800
809
  ```mt
801
- defer cleanup()
810
+ defer: cleanup()
802
811
 
803
812
  # or
804
813
 
@@ -807,7 +816,10 @@ defer:
807
816
  release_b()
808
817
  ```
809
818
 
810
- `return` is not allowed inside defer blocks.
819
+ Rules:
820
+
821
+ - `defer` always requires `:`. A single statement may follow on the same line (`defer: release_a()`), or a `:`-newline-indented block may hold several statements.
822
+ - `return` is not allowed inside defer blocks.
811
823
 
812
824
  ### 4.5 Unsafe
813
825
 
@@ -1363,8 +1375,8 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
1363
1375
  let closed_sub = window.closed.subscribe(on_close)?
1364
1376
  let resized_sub = window.resized.subscribe(on_resize)?
1365
1377
 
1366
- defer window.closed.unsubscribe(closed_sub)
1367
- defer window.resized.unsubscribe(resized_sub)
1378
+ defer: window.closed.unsubscribe(closed_sub)
1379
+ defer: window.resized.unsubscribe(resized_sub)
1368
1380
 
1369
1381
  return Result[void, EventError].success()
1370
1382
  ```
data/lib/milk_tea/base.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "pathname"
4
4
 
5
5
  module MilkTea
6
- VERSION = "0.3.14"
6
+ VERSION = "0.3.16"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -212,8 +212,8 @@ module MilkTea
212
212
  ReturnStmt = Data.define(:value, :line, :column, :length) do
213
213
  def initialize(value:, line: nil, column: nil, length: nil) = super
214
214
  end
215
- DeferStmt = Data.define(:expression, :body, :line, :column, :length) do
216
- def initialize(expression:, body:, line: nil, column: nil, length: nil) = super
215
+ DeferStmt = Data.define(:body, :line, :column, :length) do
216
+ def initialize(body:, line: nil, column: nil, length: nil) = super
217
217
  end
218
218
  ErrorBlockStmt = Data.define(:body, :line, :column, :length, :message, :header_type, :header_expression, :header_bindings, :header_iterables) do
219
219
  def initialize(body:, line: nil, column: nil, length: nil, message: nil, header_type: nil, header_expression: nil, header_bindings: nil, header_iterables: nil) = super
@@ -212,8 +212,8 @@ module MilkTea
212
212
  when AST::UnsafeStmt
213
213
  build_block(stmt.body, next_id, break_target:, continue_target:)
214
214
  when AST::DeferStmt
215
- body_entry = stmt.body ? build_block(stmt.body, next_id, break_target:, continue_target:) : next_id
216
- expression_reads, expression_reads_info = stmt.expression ? read_identifiers_with_sites(stmt.expression) : [Set.new, []]
215
+ body_entry = build_block(stmt.body, next_id, break_target:, continue_target:)
216
+ expression_reads, expression_reads_info = statement_list_reads_with_sites(stmt.body)
217
217
  defer_id = @graph.add_node(
218
218
  kind: :defer,
219
219
  statement: stmt,
@@ -315,6 +315,66 @@ module MilkTea
315
315
  [reads, reads_info]
316
316
  end
317
317
 
318
+ def statement_list_reads_with_sites(statements)
319
+ reads = Set.new
320
+ reads_info = []
321
+ statement_list_read_identifiers(statements, reads, reads_info)
322
+ [reads, reads_info]
323
+ end
324
+
325
+ def statement_list_read_identifiers(statements, reads, reads_info)
326
+ Array(statements).each do |statement|
327
+ statement_read_identifiers(statement, reads, reads_info)
328
+ end
329
+ end
330
+
331
+ def statement_read_identifiers(statement, reads, reads_info)
332
+ case statement
333
+ when AST::ExpressionStmt
334
+ read_identifiers(statement.expression, reads, reads_info)
335
+ when AST::DeferStmt
336
+ statement_list_read_identifiers(statement.body, reads, reads_info)
337
+ when AST::LocalDecl
338
+ read_identifiers(statement.value, reads, reads_info)
339
+ statement_list_read_identifiers(statement.else_body, reads, reads_info)
340
+ when AST::Assignment
341
+ read_identifiers(statement.target, reads, reads_info)
342
+ read_identifiers(statement.value, reads, reads_info)
343
+ when AST::ReturnStmt
344
+ read_identifiers(statement.value, reads, reads_info)
345
+ when AST::IfStmt
346
+ statement.branches.each do |branch|
347
+ read_identifiers(branch.condition, reads, reads_info)
348
+ statement_list_read_identifiers(branch.body, reads, reads_info)
349
+ end
350
+ statement_list_read_identifiers(statement.else_body, reads, reads_info)
351
+ when AST::WhileStmt
352
+ read_identifiers(statement.condition, reads, reads_info)
353
+ statement_list_read_identifiers(statement.body, reads, reads_info)
354
+ when AST::ForStmt
355
+ statement.iterables.each { |iterable| read_identifiers(iterable, reads, reads_info) }
356
+ statement_list_read_identifiers(statement.body, reads, reads_info)
357
+ when AST::UnsafeStmt
358
+ statement_list_read_identifiers(statement.body, reads, reads_info)
359
+ when AST::MatchStmt
360
+ read_identifiers(statement.expression, reads, reads_info)
361
+ statement.arms.each do |arm|
362
+ read_identifiers(arm.pattern, reads, reads_info)
363
+ statement_list_read_identifiers(arm.body, reads, reads_info)
364
+ end
365
+ when AST::StaticAssert
366
+ read_identifiers(statement.condition, reads, reads_info)
367
+ read_identifiers(statement.message, reads, reads_info)
368
+ when AST::WhenStmt
369
+ read_identifiers(statement.discriminant, reads, reads_info)
370
+ statement.branches.each do |branch|
371
+ read_identifiers(branch.pattern, reads, reads_info)
372
+ statement_list_read_identifiers(branch.body, reads, reads_info)
373
+ end
374
+ statement_list_read_identifiers(statement.else_body, reads, reads_info)
375
+ end
376
+ end
377
+
318
378
  def read_identifiers(expression, names = Set.new, reads_info = [])
319
379
  case expression
320
380
  when nil
@@ -145,15 +145,9 @@ module MilkTea
145
145
  when AST::UnsafeStmt
146
146
  await_counter = analyze_async_statements!(statement.body, await_counter, env, param_fields, local_fields, await_fields)
147
147
  when AST::DeferStmt
148
- if statement.body
149
- cleanup_env = duplicate_env(env)
150
- cleanup_env[:return_context] = cleanup_env[:return_context]&.merge(allow_return: false)
151
- await_counter = analyze_async_statements!(statement.body, await_counter, cleanup_env, param_fields, local_fields, await_fields)
152
- end
153
- if statement.expression.is_a?(AST::AwaitExpr)
154
- await_fields[statement.expression.object_id] = build_async_await_field_info(statement.expression, await_counter, env:, param_fields:, local_fields:)
155
- await_counter += 1
156
- end
148
+ cleanup_env = duplicate_env(env)
149
+ cleanup_env[:return_context] = cleanup_env[:return_context]&.merge(allow_return: false)
150
+ await_counter = analyze_async_statements!(statement.body, await_counter, cleanup_env, param_fields, local_fields, await_fields)
157
151
  else
158
152
  nil
159
153
  end
@@ -194,7 +194,7 @@ module MilkTea
194
194
  when AST::UnsafeStmt
195
195
  statements_contain_await?(s.body, async_info)
196
196
  when AST::DeferStmt
197
- (s.body && statements_contain_await?(s.body, async_info)) || (s.expression && async_expression_contains_await?(s.expression))
197
+ statements_contain_await?(s.body, async_info)
198
198
  else
199
199
  false
200
200
  end
@@ -1237,15 +1237,7 @@ module MilkTea
1237
1237
  end
1238
1238
 
1239
1239
  def lower_async_defer_cleanup(statement, env:, async_info:)
1240
- body = if statement.body
1241
- statement.body
1242
- elsif statement.expression
1243
- [AST::ExpressionStmt.new(expression: statement.expression, line: statement.line)]
1244
- else
1245
- []
1246
- end
1247
-
1248
- { body:, env: snapshot_env(env) }
1240
+ { body: statement.body, env: snapshot_env(env) }
1249
1241
  end
1250
1242
 
1251
1243
  def lower_async_cleanup_entries(local_defers, outer_defers, frame_expr:, raw_frame_expr:, async_info:)
@@ -154,13 +154,8 @@ module MilkTea
154
154
  when AST::DeferStmt
155
155
  cleanup_env = duplicate_env(env)
156
156
  cleanup_env[:return_context] = cleanup_env[:return_context]&.merge(allow_return: false)
157
- cleanup_body = if statement.body
158
- normalize_async_statements(statement.body, counter, cleanup_env, return_type:)
159
- else
160
- expression_setup, expression = normalize_async_expression(statement.expression, counter, env: cleanup_env)
161
- expression_setup + [AST::ExpressionStmt.new(expression:, line: statement.line)]
162
- end
163
- [AST::DeferStmt.new(expression: nil, body: cleanup_body, line: statement.line, column: statement.column, length: statement.length)]
157
+ cleanup_body = normalize_async_statements(statement.body, counter, cleanup_env, return_type:)
158
+ [AST::DeferStmt.new(body: cleanup_body, line: statement.line, column: statement.column, length: statement.length)]
164
159
  when AST::BreakStmt, AST::ContinueStmt, AST::StaticAssert, AST::PassStmt
165
160
  [statement]
166
161
  else
@@ -16,11 +16,7 @@ module MilkTea
16
16
  statements.each do |statement|
17
17
  case statement
18
18
  when AST::DeferStmt
19
- local_defers << if statement.body
20
- lower_defer_cleanup_body(statement.body, env: local_env, return_type:)
21
- else
22
- lower_defer_cleanup_expression(statement.expression, env: local_env)
23
- end
19
+ local_defers << lower_defer_cleanup_body(statement.body, env: local_env, return_type:)
24
20
  when AST::UnsafeStmt
25
21
  lower_block_unsafe_stmt(statement, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
26
22
  when AST::LocalDecl
@@ -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
@@ -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
@@ -1161,7 +1135,7 @@ module MilkTea
1161
1135
  cached = cache[text]
1162
1136
  return cached if cached
1163
1137
 
1164
- identifier = text.gsub(/[^A-Za-z0-9_]+/, "_").gsub(/_+/, "_").sub(/_+$/, "").sub(/^_{2,}/, "_")
1138
+ identifier = text.gsub(/[^A-Za-z0-9_]+/, "_").gsub(/_+/, "_").sub(/_+$/, "").sub(/^_+/, "")
1165
1139
  cache[text] = identifier.empty? ? "value" : identifier
1166
1140
  end
1167
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)
@@ -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