mt-lang 0.3.24 → 0.3.26

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/lib/milk_tea/base.rb +1 -1
  3. data/lib/milk_tea/core/bindings/attribute_binding.rb +51 -49
  4. data/lib/milk_tea/core/bindings/function_binding.rb +3 -1
  5. data/lib/milk_tea/core/bindings/module_binding.rb +42 -40
  6. data/lib/milk_tea/core/bindings/value_binding.rb +16 -14
  7. data/lib/milk_tea/core/bindings.rb +4 -9
  8. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +11 -11
  9. data/lib/milk_tea/core/c_backend/expressions.rb +11 -11
  10. data/lib/milk_tea/core/c_backend/feature_detection.rb +15 -19
  11. data/lib/milk_tea/core/c_backend/format_helpers.rb +1 -1
  12. data/lib/milk_tea/core/c_backend/reinterpret.rb +10 -10
  13. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1 -1
  14. data/lib/milk_tea/core/c_backend/statements.rb +20 -20
  15. data/lib/milk_tea/core/c_backend/type_declaration.rb +4 -4
  16. data/lib/milk_tea/core/c_backend/type_system.rb +1 -1
  17. data/lib/milk_tea/core/c_backend.rb +3 -3
  18. data/lib/milk_tea/core/compile_time.rb +41 -70
  19. data/lib/milk_tea/core/control_flow/builder.rb +3 -3
  20. data/lib/milk_tea/core/control_flow/constant_propagation.rb +1 -1
  21. data/lib/milk_tea/core/control_flow/graph.rb +1 -1
  22. data/lib/milk_tea/core/control_flow/termination.rb +1 -1
  23. data/lib/milk_tea/core/cst.rb +16 -0
  24. data/lib/milk_tea/core/flow_scope.rb +13 -0
  25. data/lib/milk_tea/core/lexer/character_classes.rb +2 -2
  26. data/lib/milk_tea/core/lexer/format_strings.rb +5 -5
  27. data/lib/milk_tea/core/lexer/heredocs.rb +6 -6
  28. data/lib/milk_tea/core/lexer/indentation.rb +4 -4
  29. data/lib/milk_tea/core/lexer/numbers.rb +1 -1
  30. data/lib/milk_tea/core/lexer/strings.rb +4 -4
  31. data/lib/milk_tea/core/lexer/symbols.rb +3 -3
  32. data/lib/milk_tea/core/lexer/trivia.rb +4 -4
  33. data/lib/milk_tea/core/lexer.rb +17 -17
  34. data/lib/milk_tea/core/lowering/async/analysis.rb +210 -208
  35. data/lib/milk_tea/core/lowering/async/async_lowering.rb +1406 -0
  36. data/lib/milk_tea/core/lowering/async/frame_builder.rb +615 -613
  37. data/lib/milk_tea/core/lowering/async/normalization.rb +410 -408
  38. data/lib/milk_tea/core/lowering/block.rb +982 -980
  39. data/lib/milk_tea/core/lowering/calls.rb +1360 -1358
  40. data/lib/milk_tea/core/lowering/declarations.rb +175 -173
  41. data/lib/milk_tea/core/lowering/dyn.rb +171 -169
  42. data/lib/milk_tea/core/lowering/events.rb +946 -944
  43. data/lib/milk_tea/core/lowering/expressions.rb +1514 -1512
  44. data/lib/milk_tea/core/lowering/foreign_cstr.rb +180 -178
  45. data/lib/milk_tea/core/lowering/functions.rb +186 -184
  46. data/lib/milk_tea/core/lowering/loops.rb +1004 -1002
  47. data/lib/milk_tea/core/lowering/lowering_context.rb +1 -1
  48. data/lib/milk_tea/core/lowering/proc.rb +355 -353
  49. data/lib/milk_tea/core/lowering/resolve.rb +2127 -2125
  50. data/lib/milk_tea/core/lowering/scans.rb +162 -168
  51. data/lib/milk_tea/core/lowering/str_buffer.rb +104 -102
  52. data/lib/milk_tea/core/lowering/utils.rb +964 -963
  53. data/lib/milk_tea/core/lowering.rb +22 -22
  54. data/lib/milk_tea/core/module_binder.rb +1 -1
  55. data/lib/milk_tea/core/module_loader.rb +2 -2
  56. data/lib/milk_tea/core/parser/attributes.rb +1 -1
  57. data/lib/milk_tea/core/parser/blocks.rb +2 -2
  58. data/lib/milk_tea/core/parser/declarations.rb +15 -15
  59. data/lib/milk_tea/core/parser/expressions.rb +20 -20
  60. data/lib/milk_tea/core/parser/recovery.rb +1 -1
  61. data/lib/milk_tea/core/parser/statements.rb +3 -3
  62. data/lib/milk_tea/core/parser/types.rb +4 -4
  63. data/lib/milk_tea/core/parser.rb +39 -21
  64. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +6 -6
  65. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +5 -5
  66. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +2 -2
  67. data/lib/milk_tea/core/semantic_analyzer/calls.rb +1 -1
  68. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +4 -4
  69. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +37 -37
  70. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +4 -4
  71. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +4 -4
  72. data/lib/milk_tea/core/semantic_analyzer/generics.rb +4 -4
  73. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +7 -7
  74. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +59 -59
  75. data/lib/milk_tea/core/semantic_analyzer/statements.rb +3 -3
  76. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +1 -1
  77. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +12 -12
  78. data/lib/milk_tea/core/types/layout.rb +3 -1
  79. data/lib/milk_tea/core/types/visitor.rb +351 -349
  80. data/lib/milk_tea/core.rb +0 -2
  81. data/lib/milk_tea/tooling/cli/commands/bindgen.rb +1 -1
  82. data/lib/milk_tea/tooling/cli/commands/check.rb +5 -5
  83. data/lib/milk_tea/tooling/cli/commands/debug.rb +3 -3
  84. data/lib/milk_tea/tooling/cli/commands/deps.rb +1 -1
  85. data/lib/milk_tea/tooling/cli/commands/emit_c.rb +2 -2
  86. data/lib/milk_tea/tooling/cli/commands/format.rb +6 -6
  87. data/lib/milk_tea/tooling/cli/commands/lint.rb +3 -3
  88. data/lib/milk_tea/tooling/cli/commands/lower.rb +3 -3
  89. data/lib/milk_tea/tooling/cli/commands/parse.rb +3 -3
  90. data/lib/milk_tea/tooling/cli/commands/test.rb +3 -3
  91. data/lib/milk_tea/tooling/cli/commands/toolchain.rb +1 -1
  92. data/lib/milk_tea/tooling/cli.rb +22 -34
  93. data/lib/milk_tea/tooling/linter/flow_rules.rb +3 -3
  94. metadata +4 -5
  95. data/lib/milk_tea/core/cst_builder.rb +0 -19
  96. data/lib/milk_tea/core/lowering/async/lowering.rb +0 -1404
  97. data/lib/milk_tea/core/token_stream.rb +0 -30
@@ -58,14 +58,14 @@ module MilkTea
58
58
  start_offset = line_offset + index
59
59
  if consumed_lines == 1
60
60
  lexeme = line[index...segment.next_index]
61
- @tokens << token(cstring ? :cstring : :string, lexeme, value, line_number, index + 1, start_offset:, end_offset: line_offset + segment.next_index)
61
+ @tokens << build_token(cstring ? :cstring : :string, lexeme, value, line_number, index + 1, start_offset:, end_offset: line_offset + segment.next_index)
62
62
  return StringLexResult.new(consumed_lines:, next_index: segment.next_index)
63
63
  end
64
64
 
65
65
  end_offset = last_line_offset + last_segment_end
66
66
  lexeme = @source.byteslice(start_offset, end_offset - start_offset)
67
- @tokens << token(cstring ? :cstring : :string, lexeme, value, line_number, index + 1, start_offset:, end_offset:)
68
- emit_line_newline(last_line, last_line_number, last_line_offset, last_line_has_newline)
67
+ @tokens << build_token(cstring ? :cstring : :string, lexeme, value, line_number, index + 1, start_offset:, end_offset:)
68
+ emit_newline(last_line, last_line_number, last_line_offset, last_line_has_newline)
69
69
  StringLexResult.new(consumed_lines:, next_index: last_segment_end)
70
70
  end
71
71
 
@@ -144,7 +144,7 @@ module MilkTea
144
144
  index += 1
145
145
 
146
146
  lexeme = line[start...index]
147
- @tokens << token(:char_literal, lexeme, value, line_number, start + 1, start_offset: line_offset + start, end_offset: line_offset + index)
147
+ @tokens << build_token(:char_literal, lexeme, value, line_number, start + 1, start_offset: line_offset + start, end_offset: line_offset + index)
148
148
  index
149
149
  end
150
150
 
@@ -9,14 +9,14 @@ module MilkTea
9
9
  lexeme = line[index, 3]
10
10
  if lexeme && THREE_CHAR_TOKENS.key?(lexeme)
11
11
  type = THREE_CHAR_TOKENS.fetch(lexeme)
12
- @tokens << token(type, lexeme, nil, line_number, start + 1, start_offset: line_offset + start, end_offset: line_offset + index + 3)
12
+ @tokens << build_token(type, lexeme, nil, line_number, start + 1, start_offset: line_offset + start, end_offset: line_offset + index + 3)
13
13
  return index + 3
14
14
  end
15
15
 
16
16
  lexeme = line[index, 2]
17
17
  if lexeme && TWO_CHAR_TOKENS.key?(lexeme)
18
18
  type = TWO_CHAR_TOKENS.fetch(lexeme)
19
- @tokens << token(type, lexeme, nil, line_number, start + 1, start_offset: line_offset + start, end_offset: line_offset + index + 2)
19
+ @tokens << build_token(type, lexeme, nil, line_number, start + 1, start_offset: line_offset + start, end_offset: line_offset + index + 2)
20
20
  adjust_grouping_depth(type, line_number, start + 1)
21
21
  return index + 2
22
22
  end
@@ -37,7 +37,7 @@ module MilkTea
37
37
  raise LexError.new("unexpected character #{lexeme.inspect}", line: line_number, column: start + 1, path: @path)
38
38
  end
39
39
 
40
- @tokens << token(type, lexeme, nil, line_number, start + 1, start_offset: line_offset + start, end_offset: line_offset + index + 1)
40
+ @tokens << build_token(type, lexeme, nil, line_number, start + 1, start_offset: line_offset + start, end_offset: line_offset + index + 1)
41
41
  adjust_grouping_depth(type, line_number, start + 1)
42
42
  index + 1
43
43
  end
@@ -9,7 +9,7 @@ module MilkTea
9
9
  @mode == :with_trivia
10
10
  end
11
11
 
12
- def register_detached_line_trivia(kind, line, line_number, line_offset, has_newline:)
12
+ def record_line_trivia(kind, line, line_number, line_offset, has_newline:)
13
13
  return unless with_trivia?
14
14
 
15
15
  text = has_newline ? (line + "\n") : line
@@ -21,17 +21,17 @@ module MilkTea
21
21
  start_offset: line_offset,
22
22
  end_offset: line_offset + text.bytesize,
23
23
  )
24
- push_pending_leading_trivia(trivia)
24
+ push_leading_trivia(trivia)
25
25
  end
26
26
 
27
- def push_pending_leading_trivia(trivia)
27
+ def push_leading_trivia(trivia)
28
28
  return unless with_trivia?
29
29
 
30
30
  @trivia << trivia
31
31
  @pending_leading_trivia << trivia
32
32
  end
33
33
 
34
- def append_trailing_or_pending(trivia)
34
+ def attach_trivia(trivia)
35
35
  return unless with_trivia?
36
36
 
37
37
  @trivia << trivia
@@ -171,10 +171,10 @@ module MilkTea
171
171
 
172
172
  while @indent_stack.length > 1
173
173
  @indent_stack.pop
174
- @tokens << token(:dedent, "", nil, @line_count, 1, start_offset: @source.bytesize, end_offset: @source.bytesize)
174
+ @tokens << build_token(:dedent, "", nil, @line_count, 1, start_offset: @source.bytesize, end_offset: @source.bytesize)
175
175
  end
176
176
 
177
- @tokens << token(:eof, "", nil, @line_count + 1, 1, start_offset: @source.bytesize, end_offset: @source.bytesize)
177
+ @tokens << build_token(:eof, "", nil, @line_count + 1, 1, start_offset: @source.bytesize, end_offset: @source.bytesize)
178
178
  return LexResult.new(tokens: @tokens, trivia: @trivia) if with_trivia?
179
179
 
180
180
  @tokens
@@ -193,12 +193,12 @@ module MilkTea
193
193
  end
194
194
 
195
195
  if line.strip.empty?
196
- register_detached_line_trivia(:blank_line, line, line_number, line_offset, has_newline:)
196
+ record_line_trivia(:blank_line, line, line_number, line_offset, has_newline:)
197
197
  return 1
198
198
  end
199
199
 
200
200
  if line.lstrip.start_with?("#")
201
- register_detached_line_trivia(:comment, line, line_number, line_offset, has_newline:)
201
+ record_line_trivia(:comment, line, line_number, line_offset, has_newline:)
202
202
  return 1
203
203
  end
204
204
 
@@ -206,11 +206,11 @@ module MilkTea
206
206
  if @recovery_errors && @grouping_depth.positive? && index.zero? && top_level_resync_line?(line)
207
207
  @recovery_errors << LexError.new("unclosed grouping delimiter", line: @grouping_start_line, column: @grouping_start_column, path: @path)
208
208
  @grouping_depth = 0
209
- @tokens << token(:newline, "\n", nil, line_number, 1, start_offset: line_offset, end_offset: line_offset)
209
+ @tokens << build_token(:newline, "\n", nil, line_number, 1, start_offset: line_offset, end_offset: line_offset)
210
210
  end
211
211
 
212
212
  if with_trivia? && index.positive?
213
- push_pending_leading_trivia(
213
+ push_leading_trivia(
214
214
  TriviaToken.new(
215
215
  kind: :space,
216
216
  text: line[0...index],
@@ -244,7 +244,7 @@ module MilkTea
244
244
  if with_trivia?
245
245
  span_start = index
246
246
  index += 1 while index < line.length && line[index] == " "
247
- push_pending_leading_trivia(
247
+ push_leading_trivia(
248
248
  TriviaToken.new(
249
249
  kind: :space,
250
250
  text: line[span_start...index],
@@ -265,7 +265,7 @@ module MilkTea
265
265
  if with_trivia?
266
266
  comment_end = line.length
267
267
  comment_text = line[index...comment_end]
268
- append_trailing_or_pending(
268
+ attach_trivia(
269
269
  TriviaToken.new(
270
270
  kind: :comment,
271
271
  text: comment_text,
@@ -280,7 +280,7 @@ module MilkTea
280
280
  end
281
281
 
282
282
  if (char == "c" || char == "f")
283
- result = dispatch_cf_prefix(char, line, index, line_number, line_offset, lines:, line_index:)
283
+ result = dispatch_prefixed_literal(char, line, index, line_number, line_offset, lines:, line_index:)
284
284
  if result
285
285
  if result[0] == :return
286
286
  return result[1]
@@ -327,10 +327,10 @@ module MilkTea
327
327
  if Token::LINE_CONTINUATION_OPERATORS.include?(@tokens.last&.type)
328
328
  @continuation_pending = true
329
329
  else
330
- @tokens << token(:newline, "\n", nil, line_number, line.length + 1, start_offset: newline_start, end_offset: newline_end)
330
+ @tokens << build_token(:newline, "\n", nil, line_number, line.length + 1, start_offset: newline_start, end_offset: newline_end)
331
331
  end
332
332
  elsif with_trivia? && has_newline
333
- append_trailing_or_pending(
333
+ attach_trivia(
334
334
  TriviaToken.new(
335
335
  kind: :newline,
336
336
  text: "\n",
@@ -345,14 +345,14 @@ module MilkTea
345
345
  1
346
346
  end
347
347
 
348
- def emit_line_newline(line, line_number, line_offset, has_newline)
348
+ def emit_newline(line, line_number, line_offset, has_newline)
349
349
  newline_start = line_offset + line.bytesize
350
350
  newline_end = has_newline ? (newline_start + 1) : newline_start
351
351
 
352
352
  if @grouping_depth.zero?
353
- @tokens << token(:newline, "\n", nil, line_number, line.bytesize + 1, start_offset: newline_start, end_offset: newline_end)
353
+ @tokens << build_token(:newline, "\n", nil, line_number, line.bytesize + 1, start_offset: newline_start, end_offset: newline_end)
354
354
  elsif with_trivia? && has_newline
355
- append_trailing_or_pending(
355
+ attach_trivia(
356
356
  TriviaToken.new(
357
357
  kind: :newline,
358
358
  text: "\n",
@@ -365,7 +365,7 @@ module MilkTea
365
365
  end
366
366
  end
367
367
 
368
- def dispatch_cf_prefix(char, line, index, line_number, line_offset, lines:, line_index:)
368
+ def dispatch_prefixed_literal(char, line, index, line_number, line_offset, lines:, line_index:)
369
369
  return nil unless char == "c" || char == "f"
370
370
 
371
371
  is_c = char == "c"
@@ -377,7 +377,7 @@ module MilkTea
377
377
  end
378
378
 
379
379
  if line[index + 1] == "<" && line[index + 2] == "-" && identifier_start?(line[index + 3])
380
- error = LexError.new("expected '<<-' for heredoc string; did you mean '#{char}<<-#{identifier_start_token(line, index + 3)}'?", line: line_number, column: index + 1, path: @path)
380
+ error = LexError.new("expected '<<-' for heredoc string; did you mean '#{char}<<-#{extract_identifier_at(line, index + 3)}'?", line: line_number, column: index + 1, path: @path)
381
381
  if @recovery_errors
382
382
  @recovery_errors << error
383
383
  else
@@ -402,7 +402,7 @@ module MilkTea
402
402
  nil
403
403
  end
404
404
 
405
- def token(type, lexeme, literal, line, column, start_offset:, end_offset:)
405
+ def build_token(type, lexeme, literal, line, column, start_offset:, end_offset:)
406
406
  Token.new(
407
407
  type:,
408
408
  lexeme:,
@@ -1,237 +1,239 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MilkTea
4
- module LowererAsync
5
- def analyze_async_function(binding, statements)
6
- env = empty_env
7
- void_ptr = pointer_to(@ctx.types.fetch("void"))
8
- wake_type = Types::Registry.function(
9
- nil,
10
- params: [Types::Registry.parameter("frame", void_ptr)],
11
- return_type: @ctx.types.fetch("void"),
12
- )
13
- param_fields = {}
14
- local_fields = {}
15
- await_fields = {}
16
- await_counter = 0
17
-
18
- binding.body_params.each do |param_binding|
19
- pointer = binding.type.receiver_type && binding.type.receiver_editable && param_binding.name == "this"
20
- field_type = pointer ? pointer_to(param_binding.type) : param_binding.type
21
- field_name = "param_#{param_binding.name}"
22
- param_fields[param_binding.name] = {
23
- field_name:,
24
- type: field_type,
25
- param_type: param_binding.type,
26
- mutable: param_binding.mutable,
27
- pointer:,
28
- }
29
- env[:scopes].last[param_binding.name] = local_binding(
30
- type: param_binding.type,
31
- linkage_name: field_name,
32
- mutable: param_binding.mutable,
33
- pointer:,
4
+ module Lowering
5
+ module Async
6
+ def analyze_async_function(binding, statements)
7
+ env = empty_env
8
+ void_ptr = pointer_to(@ctx.types.fetch("void"))
9
+ wake_type = Types::Registry.function(
10
+ nil,
11
+ params: [Types::Registry.parameter("frame", void_ptr)],
12
+ return_type: @ctx.types.fetch("void"),
34
13
  )
14
+ param_fields = {}
15
+ local_fields = {}
16
+ await_fields = {}
17
+ await_counter = 0
18
+
19
+ binding.body_params.each do |param_binding|
20
+ pointer = binding.type.receiver_type && binding.type.receiver_editable && param_binding.name == "this"
21
+ field_type = pointer ? pointer_to(param_binding.type) : param_binding.type
22
+ field_name = "param_#{param_binding.name}"
23
+ param_fields[param_binding.name] = {
24
+ field_name:,
25
+ type: field_type,
26
+ param_type: param_binding.type,
27
+ mutable: param_binding.mutable,
28
+ pointer:,
29
+ }
30
+ env[:scopes].last[param_binding.name] = local_binding(
31
+ type: param_binding.type,
32
+ linkage_name: field_name,
33
+ mutable: param_binding.mutable,
34
+ pointer:,
35
+ )
36
+ end
37
+ env[:return_context] = {
38
+ return_type: binding.body_return_type,
39
+ active_defers: [],
40
+ local_defers: [],
41
+ allow_return: true,
42
+ }
43
+
44
+ await_counter = analyze_async_statements!(statements, await_counter, env, param_fields, local_fields, await_fields)
45
+
46
+ {
47
+ task_type: binding.type.return_type,
48
+ result_type: binding.body_return_type,
49
+ void_ptr:,
50
+ wake_type:,
51
+ param_fields:,
52
+ local_fields:,
53
+ await_fields:,
54
+ format_str_fields: {},
55
+ }
35
56
  end
36
- env[:return_context] = {
37
- return_type: binding.body_return_type,
38
- active_defers: [],
39
- local_defers: [],
40
- allow_return: true,
41
- }
42
-
43
- await_counter = analyze_async_statements!(statements, await_counter, env, param_fields, local_fields, await_fields)
44
-
45
- {
46
- task_type: binding.type.return_type,
47
- result_type: binding.body_return_type,
48
- void_ptr:,
49
- wake_type:,
50
- param_fields:,
51
- local_fields:,
52
- await_fields:,
53
- format_str_fields: {},
54
- }
55
- end
56
57
 
57
- # Recursively scan nested statement bodies for await slots, assigning state IDs.
58
- # Returns the updated await_counter.
59
- def analyze_async_statements!(statements, await_counter, env, param_fields, local_fields, await_fields)
60
- statements.each do |statement|
61
- case statement
62
- when AST::LocalDecl
63
- type, storage_type = async_local_decl_types(statement, env:)
64
- local_field_key = async_local_decl_field_key(statement)
65
- local_fields[local_field_key] ||= { field_name: async_local_decl_field_name(statement), type:, storage_type:, mutable: statement.kind == :var }
66
- if statement.value.is_a?(AST::AwaitExpr)
67
- await_fields[statement.value.object_id] = build_async_await_field_info(statement.value, await_counter, env:, param_fields:, local_fields:)
68
- await_counter += 1
69
- end
70
- if bind_let_else_local?(statement)
71
- env[:scopes].last[statement.name] = local_binding(
72
- type:,
73
- storage_type:,
74
- linkage_name: statement.name,
75
- mutable: statement.kind == :var,
76
- pointer: false,
77
- const_value: statement.else_body ? nil : statement.kind == :let && statement.value ? compile_time_const_value(statement.value, env:) : nil,
78
- )
79
- end
80
- await_counter = analyze_async_statements!(statement.else_body, await_counter, env, param_fields, local_fields, await_fields) if statement.else_body
81
- when AST::Assignment
82
- if statement.value.is_a?(AST::AwaitExpr)
83
- await_fields[statement.value.object_id] = build_async_await_field_info(statement.value, await_counter, env:, param_fields:, local_fields:)
84
- await_counter += 1
85
- end
86
- when AST::ExpressionStmt
87
- if statement.expression.is_a?(AST::AwaitExpr)
88
- await_fields[statement.expression.object_id] = build_async_await_field_info(statement.expression, await_counter, env:, param_fields:, local_fields:)
89
- await_counter += 1
90
- end
91
- when AST::ReturnStmt
92
- if statement.value&.is_a?(AST::AwaitExpr)
93
- await_fields[statement.value.object_id] = build_async_await_field_info(statement.value, await_counter, env:, param_fields:, local_fields:)
94
- await_counter += 1
95
- end
96
- when AST::IfStmt
97
- statement.branches.each do |branch|
98
- await_counter = analyze_async_statements!(branch.body, await_counter, env, param_fields, local_fields, await_fields)
99
- end
100
- await_counter = analyze_async_statements!(statement.else_body, await_counter, env, param_fields, local_fields, await_fields) if statement.else_body
101
- when AST::WhileStmt
102
- await_counter = analyze_async_statements!(statement.body, await_counter, env, param_fields, local_fields, await_fields)
103
- when AST::ForStmt
104
- if range_iterable?(statement.iterable)
105
- loop_type = infer_range_loop_type(statement.iterable, env:)
106
- local_fields[statement.name] ||= { field_name: "local_#{statement.name}", type: loop_type, storage_type: loop_type, mutable: true }
107
- stop_field_name = "local_#{statement.name}_stop"
108
- local_fields[stop_field_name] ||= { field_name: stop_field_name, type: loop_type, storage_type: loop_type, mutable: true }
109
- else
110
- statement.bindings.each_with_index do |binding, index|
111
- iterable_type = infer_expression_type(statement.iterables[index], env:)
112
- element_type = collection_loop_type(iterable_type)
113
- binding_type = collection_loop_binding_type(iterable_type, element_type) || element_type
114
- local_fields[binding.name] ||= { field_name: "local_#{binding.name}", type: binding_type, storage_type: binding_type, mutable: true }
115
- iterable_field_name = async_collection_iterable_field_name(statement, index)
116
- iterable_field_key = async_collection_iterable_field_key(statement, index)
117
- local_fields[iterable_field_key] ||= { field_name: iterable_field_name, type: iterable_type, storage_type: iterable_type, mutable: true }
58
+ # Recursively scan nested statement bodies for await slots, assigning state IDs.
59
+ # Returns the updated await_counter.
60
+ def analyze_async_statements!(statements, await_counter, env, param_fields, local_fields, await_fields)
61
+ statements.each do |statement|
62
+ case statement
63
+ when AST::LocalDecl
64
+ type, storage_type = async_local_decl_types(statement, env:)
65
+ local_field_key = async_local_decl_field_key(statement)
66
+ local_fields[local_field_key] ||= { field_name: async_local_decl_field_name(statement), type:, storage_type:, mutable: statement.kind == :var }
67
+ if statement.value.is_a?(AST::AwaitExpr)
68
+ await_fields[statement.value.object_id] = build_async_await_field_info(statement.value, await_counter, env:, param_fields:, local_fields:)
69
+ await_counter += 1
118
70
  end
119
- index_field_name = async_collection_index_field_name(statement)
120
- index_field_key = async_collection_index_field_key(statement)
121
- local_fields[index_field_key] ||= { field_name: index_field_name, type: @ctx.types.fetch("ptr_uint"), storage_type: @ctx.types.fetch("ptr_uint"), mutable: true }
122
- end
123
- await_counter = analyze_async_statements!(statement.body, await_counter, env, param_fields, local_fields, await_fields)
124
- when AST::MatchStmt
125
- scrutinee_type = infer_expression_type(statement.expression, env:)
126
- statement.arms.each do |arm|
127
- arm_env = duplicate_env(env)
128
- bind_async_variant_match_arm_env!(arm_env, scrutinee_type, arm)
129
- arm_await_count = await_counter
130
- await_counter = analyze_async_statements!(arm.body, await_counter, arm_env, param_fields, local_fields, await_fields)
131
- if arm.binding_name && await_counter > arm_await_count
132
- field_key = async_match_binding_field_key(arm)
133
- unless local_fields.key?(field_key)
134
- arm_name = variant_match_arm_name_from_pattern(arm.pattern)
135
- if arm_name
136
- arm_binding = arm_env[:scopes].last[arm.binding_name]
137
- payload_type = arm_binding && arm_binding[:type]
138
- if payload_type
139
- local_fields[field_key] = { field_name: async_match_binding_field_name(arm), type: payload_type, storage_type: payload_type, mutable: false }
71
+ if bind_let_else_local?(statement)
72
+ env[:scopes].last[statement.name] = local_binding(
73
+ type:,
74
+ storage_type:,
75
+ linkage_name: statement.name,
76
+ mutable: statement.kind == :var,
77
+ pointer: false,
78
+ const_value: statement.else_body ? nil : statement.kind == :let && statement.value ? compile_time_const_value(statement.value, env:) : nil,
79
+ )
80
+ end
81
+ await_counter = analyze_async_statements!(statement.else_body, await_counter, env, param_fields, local_fields, await_fields) if statement.else_body
82
+ when AST::Assignment
83
+ if statement.value.is_a?(AST::AwaitExpr)
84
+ await_fields[statement.value.object_id] = build_async_await_field_info(statement.value, await_counter, env:, param_fields:, local_fields:)
85
+ await_counter += 1
86
+ end
87
+ when AST::ExpressionStmt
88
+ if statement.expression.is_a?(AST::AwaitExpr)
89
+ await_fields[statement.expression.object_id] = build_async_await_field_info(statement.expression, await_counter, env:, param_fields:, local_fields:)
90
+ await_counter += 1
91
+ end
92
+ when AST::ReturnStmt
93
+ if statement.value&.is_a?(AST::AwaitExpr)
94
+ await_fields[statement.value.object_id] = build_async_await_field_info(statement.value, await_counter, env:, param_fields:, local_fields:)
95
+ await_counter += 1
96
+ end
97
+ when AST::IfStmt
98
+ statement.branches.each do |branch|
99
+ await_counter = analyze_async_statements!(branch.body, await_counter, env, param_fields, local_fields, await_fields)
100
+ end
101
+ await_counter = analyze_async_statements!(statement.else_body, await_counter, env, param_fields, local_fields, await_fields) if statement.else_body
102
+ when AST::WhileStmt
103
+ await_counter = analyze_async_statements!(statement.body, await_counter, env, param_fields, local_fields, await_fields)
104
+ when AST::ForStmt
105
+ if range_iterable?(statement.iterable)
106
+ loop_type = infer_range_loop_type(statement.iterable, env:)
107
+ local_fields[statement.name] ||= { field_name: "local_#{statement.name}", type: loop_type, storage_type: loop_type, mutable: true }
108
+ stop_field_name = "local_#{statement.name}_stop"
109
+ local_fields[stop_field_name] ||= { field_name: stop_field_name, type: loop_type, storage_type: loop_type, mutable: true }
110
+ else
111
+ statement.bindings.each_with_index do |binding, index|
112
+ iterable_type = infer_expression_type(statement.iterables[index], env:)
113
+ element_type = collection_loop_type(iterable_type)
114
+ binding_type = collection_loop_binding_type(iterable_type, element_type) || element_type
115
+ local_fields[binding.name] ||= { field_name: "local_#{binding.name}", type: binding_type, storage_type: binding_type, mutable: true }
116
+ iterable_field_name = async_collection_iterable_field_name(statement, index)
117
+ iterable_field_key = async_collection_iterable_field_key(statement, index)
118
+ local_fields[iterable_field_key] ||= { field_name: iterable_field_name, type: iterable_type, storage_type: iterable_type, mutable: true }
119
+ end
120
+ index_field_name = async_collection_index_field_name(statement)
121
+ index_field_key = async_collection_index_field_key(statement)
122
+ local_fields[index_field_key] ||= { field_name: index_field_name, type: @ctx.types.fetch("ptr_uint"), storage_type: @ctx.types.fetch("ptr_uint"), mutable: true }
123
+ end
124
+ await_counter = analyze_async_statements!(statement.body, await_counter, env, param_fields, local_fields, await_fields)
125
+ when AST::MatchStmt
126
+ scrutinee_type = infer_expression_type(statement.expression, env:)
127
+ statement.arms.each do |arm|
128
+ arm_env = duplicate_env(env)
129
+ bind_async_variant_match_arm_env!(arm_env, scrutinee_type, arm)
130
+ arm_await_count = await_counter
131
+ await_counter = analyze_async_statements!(arm.body, await_counter, arm_env, param_fields, local_fields, await_fields)
132
+ if arm.binding_name && await_counter > arm_await_count
133
+ field_key = async_match_binding_field_key(arm)
134
+ unless local_fields.key?(field_key)
135
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
136
+ if arm_name
137
+ arm_binding = arm_env[:scopes].last[arm.binding_name]
138
+ payload_type = arm_binding && arm_binding[:type]
139
+ if payload_type
140
+ local_fields[field_key] = { field_name: async_match_binding_field_name(arm), type: payload_type, storage_type: payload_type, mutable: false }
141
+ end
140
142
  end
141
143
  end
142
144
  end
143
145
  end
146
+ when AST::UnsafeStmt
147
+ await_counter = analyze_async_statements!(statement.body, await_counter, env, param_fields, local_fields, await_fields)
148
+ when AST::DeferStmt
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
+ else
153
+ nil
144
154
  end
145
- when AST::UnsafeStmt
146
- await_counter = analyze_async_statements!(statement.body, await_counter, env, param_fields, local_fields, await_fields)
147
- when AST::DeferStmt
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)
151
- else
152
- nil
153
155
  end
156
+ await_counter
154
157
  end
155
- await_counter
156
- end
157
158
 
158
- def async_local_decl_types(statement, env:)
159
- storage_type = if statement.else_body
160
- infer_expression_type(statement.value, env:)
161
- elsif statement.type
162
- resolve_type_ref(statement.type)
163
- else
164
- infer_expression_type(statement.value, env:)
165
- end
166
- type = if statement.else_body
167
- statement.type ? resolve_type_ref(statement.type) : let_else_success_type(storage_type)
168
- else
169
- storage_type
170
- end
171
-
172
- [type, storage_type]
173
- end
159
+ def async_local_decl_types(statement, env:)
160
+ storage_type = if statement.else_body
161
+ infer_expression_type(statement.value, env:)
162
+ elsif statement.type
163
+ resolve_type_ref(statement.type)
164
+ else
165
+ infer_expression_type(statement.value, env:)
166
+ end
167
+ type = if statement.else_body
168
+ statement.type ? resolve_type_ref(statement.type) : let_else_success_type(storage_type)
169
+ else
170
+ storage_type
171
+ end
174
172
 
175
- def async_collection_iterable_field_key(statement, index = 0)
176
- "__async_for_iterable_#{statement.line}_#{index}"
177
- end
173
+ [type, storage_type]
174
+ end
178
175
 
179
- def async_collection_iterable_field_name(statement, index = 0)
180
- "for_iterable_#{statement.line}_#{index}"
181
- end
176
+ def async_collection_iterable_field_key(statement, index = 0)
177
+ "__async_for_iterable_#{statement.line}_#{index}"
178
+ end
182
179
 
183
- def async_collection_index_field_key(statement)
184
- "__async_for_index_#{statement.line}"
185
- end
180
+ def async_collection_iterable_field_name(statement, index = 0)
181
+ "for_iterable_#{statement.line}_#{index}"
182
+ end
186
183
 
187
- def async_collection_index_field_name(statement)
188
- "for_index_#{statement.line}"
189
- end
184
+ def async_collection_index_field_key(statement)
185
+ "__async_for_index_#{statement.line}"
186
+ end
190
187
 
191
- def build_async_await_field_info(await_expression, await_counter, env:, param_fields:, local_fields:)
192
- task_expression = await_expression.expression
193
- reused_field_name = reusable_async_await_task_field_name(task_expression, param_fields:, local_fields:)
194
- {
195
- field_name: reused_field_name || "await_#{await_counter}",
196
- task_type: infer_expression_type(task_expression, env:),
197
- result_type: infer_expression_type(await_expression, env:),
198
- state: await_counter + 1,
199
- reuse_existing_storage: !reused_field_name.nil?,
200
- }
201
- end
188
+ def async_collection_index_field_name(statement)
189
+ "for_index_#{statement.line}"
190
+ end
202
191
 
203
- def reusable_async_await_task_field_name(task_expression, param_fields:, local_fields:)
204
- return unless task_expression.is_a?(AST::Identifier)
205
- return local_fields.fetch(task_expression.name)[:field_name] if local_fields.key?(task_expression.name)
206
- return param_fields.fetch(task_expression.name)[:field_name] if param_fields.key?(task_expression.name)
192
+ def build_async_await_field_info(await_expression, await_counter, env:, param_fields:, local_fields:)
193
+ task_expression = await_expression.expression
194
+ reused_field_name = reusable_async_await_task_field_name(task_expression, param_fields:, local_fields:)
195
+ {
196
+ field_name: reused_field_name || "await_#{await_counter}",
197
+ task_type: infer_expression_type(task_expression, env:),
198
+ result_type: infer_expression_type(await_expression, env:),
199
+ state: await_counter + 1,
200
+ reuse_existing_storage: !reused_field_name.nil?,
201
+ }
202
+ end
207
203
 
208
- nil
209
- end
204
+ def reusable_async_await_task_field_name(task_expression, param_fields:, local_fields:)
205
+ return unless task_expression.is_a?(AST::Identifier)
206
+ return local_fields.fetch(task_expression.name)[:field_name] if local_fields.key?(task_expression.name)
207
+ return param_fields.fetch(task_expression.name)[:field_name] if param_fields.key?(task_expression.name)
210
208
 
211
- def build_async_frame_type(frame_linkage_name, async_info)
212
- fields = {
213
- "ready" => @ctx.types.fetch("bool"),
214
- "cancelled" => @ctx.types.fetch("bool"),
215
- "waiter_frame" => async_info[:void_ptr],
216
- "waiter" => async_info[:wake_type],
217
- }
218
- fields["state"] = @ctx.types.fetch("int") unless async_info[:await_fields].empty?
219
- unless async_info[:result_type] == @ctx.types.fetch("void")
220
- fields["result"] = async_info[:result_type]
221
- end
222
- async_info[:param_fields].each_value do |field_info|
223
- fields[field_info[:field_name]] = field_info[:type]
209
+ nil
224
210
  end
225
- async_info[:local_fields].each_value do |field_info|
226
- fields[field_info[:field_name]] = field_info[:storage_type]
227
- end
228
- async_info[:await_fields].each_value do |field_info|
229
- next if fields.key?(field_info[:field_name])
230
211
 
231
- fields[field_info[:field_name]] = field_info[:task_type]
232
- end
212
+ def build_async_frame_type(frame_linkage_name, async_info)
213
+ fields = {
214
+ "ready" => @ctx.types.fetch("bool"),
215
+ "cancelled" => @ctx.types.fetch("bool"),
216
+ "waiter_frame" => async_info[:void_ptr],
217
+ "waiter" => async_info[:wake_type],
218
+ }
219
+ fields["state"] = @ctx.types.fetch("int") unless async_info[:await_fields].empty?
220
+ unless async_info[:result_type] == @ctx.types.fetch("void")
221
+ fields["result"] = async_info[:result_type]
222
+ end
223
+ async_info[:param_fields].each_value do |field_info|
224
+ fields[field_info[:field_name]] = field_info[:type]
225
+ end
226
+ async_info[:local_fields].each_value do |field_info|
227
+ fields[field_info[:field_name]] = field_info[:storage_type]
228
+ end
229
+ async_info[:await_fields].each_value do |field_info|
230
+ next if fields.key?(field_info[:field_name])
233
231
 
234
- Types::Struct.new(frame_linkage_name).define_fields(fields)
232
+ fields[field_info[:field_name]] = field_info[:task_type]
233
+ end
234
+
235
+ Types::Struct.new(frame_linkage_name).define_fields(fields)
236
+ end
235
237
  end
236
238
  end
237
239
  end