mt-lang 0.3.34 → 0.3.37

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d0786ef5ca9025e069a44b7ea3915199110e1f2397d294ed81122a308c79e2e
4
- data.tar.gz: 42e3fa0077192377fa7da61cea865fd04e8ef49d66343c71a1bd5e2e1caa8b59
3
+ metadata.gz: 84f8e5a5913811a35cd5cd470c43f3827aee62bfe5b151add730d45fe341bee2
4
+ data.tar.gz: fd76e0d6f88abfe0fda004829430effc5b7030957754b6d3d3eebbd6739b96b5
5
5
  SHA512:
6
- metadata.gz: fd4a874279ff48b7d0f1de8baf092e92e01ede8a4c969133855c9dd9c9f061490c11ff29dfc24905466fe1df56d1d53a86a5c156ce1cbe5efeed60d78f5a21e8
7
- data.tar.gz: 30bb123ddb1939f7694c62dae186152b8f592d78826e7b4aa416c6a010202d07bda5474b72014114d8064b7168ff02b6b7dd252a8b7d007d9b9bcae8b31b4786
6
+ metadata.gz: cc533975db0511ab0f5ac62887ec101ebae699841d3cdbc5079faf1e2a0be877e1ceb71d56b3605ca7c37c60c43019fd3288e690325a21cf87806ca2cff138b5
7
+ data.tar.gz: 89d0efc35e9bfec7c1d9c5a612e2f136d3c6fd8f80259f88a657ac46be896ff79c43c0810f98eba8312139151c343c59205b2a3b5fd72af60d2de2fdfc0fd16a
data/README.md CHANGED
@@ -939,7 +939,7 @@ See module source for full method surface. Iterator forms:
939
939
 
940
940
  Text categories:
941
941
 
942
- - `str` -> string view. The `+` operator concatenates two `str` values, allocating a new heap-backed string. For loops or repeated concatenation, prefer `string.String` for amortized performance.
942
+ - `str` -> string view. The `+` operator concatenates two `str` values into a per-thread scratch buffer (no heap allocation), returning a borrowed `str`; the result stays valid while cumulative concatenations on that thread remain within the scratch buffer budget. For loops or repeated concatenation, prefer `string.String` for amortized building.
943
943
  - `cstr` -> C ABI string
944
944
  - `str_buffer[N]` -> fixed-capacity mutable UTF-8 text buffer
945
945
 
@@ -959,7 +959,7 @@ Format strings:
959
959
 
960
960
  - `f"count=#{count}"` has type `str`.
961
961
  - Allowed interpolations: `str`, `cstr`, `bool`, numeric primitives, integer-backed enums and flags, plus types implementing `format_len() -> ptr_uint` and `append_format(output: ref[std.string.String]) -> void`.
962
- - `f"..."` is a borrowed temporary on the stack it cannot be returned from a function as `str`. Use `std.fmt.format(f"...")` returning `string.String` when ownership must escape.
962
+ - Dynamic `f"..."` expressions build into a heap-backed temporary that the compiler releases after use. A dynamic `f"..."` may be returned from a function (or stored in a `str` local): ownership of that buffer transfers to the caller. `std.fmt.format(f"...")` returns an owned `string.String` when you want explicit owned-text lifetime management.
963
963
  - Float and double interpolations support `:.N` precision.
964
964
  - Integer primitive and integer-backed enum/flags interpolations support `:x` (lowercase hex) and `:X` (uppercase hex).
965
965
  - Integer primitive and integer-backed enum/flags interpolations support `:o` / `:O` (octal) and `:b` / `:B` (binary).
data/docs/index.html CHANGED
@@ -1262,7 +1262,7 @@ let d: dyn[Drawable] = adapt[Drawable](ref_of(entity))</code></pre>
1262
1262
  <tr><td><code>ptr_int</code> <code>ptr_uint</code></td><td>Pointer-sized integers</td></tr>
1263
1263
  <tr><td><code>float</code> <code>double</code></td><td>Floating-point</td></tr>
1264
1264
  <tr><td><code>void</code></td><td>No value</td></tr>
1265
- <tr><td><code>str</code></td><td>UTF-8 string view (borrowed). The <code>+</code> operator concatenates two <code>str</code> values, allocating a new heap-backed string.</td></tr>
1265
+ <tr><td><code>str</code></td><td>UTF-8 string view (borrowed). The <code>+</code> operator concatenates two <code>str</code> values into a per-thread scratch buffer (no allocation); the result is valid while the scratch budget lasts.</td></tr>
1266
1266
  <tr><td><code>cstr</code></td><td>NUL-terminated C string</td></tr>
1267
1267
  <tr><td><code>vec2</code> <code>vec3</code> <code>vec4</code></td><td>Float vectors with <code>.x .y .z .w</code></td></tr>
1268
1268
  <tr><td><code>ivec2</code> <code>ivec3</code> <code>ivec4</code></td><td>Integer vectors</td></tr>
@@ -1871,7 +1871,7 @@ function field_equal[T](a: const_ptr[T], b: const_ptr[T]) -&gt; bool:
1871
1871
  <div class="table-wrap">
1872
1872
  <table class="attr-table">
1873
1873
  <tr><th>Type</th><th>Ownership</th><th>Use Case</th></tr>
1874
- <tr><td><code>str</code></td><td>Borrowed</td><td>Read-only UTF-8 view (literals, format strings, slicing)</td></tr>
1874
+ <tr><td><code>str</code></td><td>Borrowed</td><td>Read-only UTF-8 view (literals, format strings, slicing); a stored or returned dynamic format string carries its own heap buffer</td></tr>
1875
1875
  <tr><td><code>cstr</code></td><td>Borrowed</td><td>NUL-terminated C ABI string (<code>c"hello"</code>)</td></tr>
1876
1876
  <tr><td><code>str_buffer[N]</code></td><td>Owned (stack)</td><td>Fixed-capacity mutable UTF-8 builder</td></tr>
1877
1877
  <tr><td><code>std.string.String</code></td><td>Owned (heap)</td><td>Growable owned text via <code>fmt.format(f"...")</code></td></tr>
@@ -1889,9 +1889,9 @@ let info = f"value=#{pi:.4}"
1889
1889
  ## Hex format for ints
1890
1890
  let hex = f"address=#{ptr_value:x}"
1891
1891
 
1892
- ## Owned text (escape stack lifetime)
1892
+ ## Owned text with explicit release
1893
1893
  import std.fmt
1894
- let owned = fmt.format(f"count=#{count}") ## -> string.String</code></pre>
1894
+ let owned = fmt.format(f"count=#{count}") ## -> string.String (call .release())</code></pre>
1895
1895
  </div>
1896
1896
  <p>Interpolated expressions must be <code>str</code>, <code>cstr</code>, <code>bool</code>, a numeric primitive, an integer-backed enum or flags type, or a type implementing <code>format_len()</code> and <code>append_format()</code> (custom formatting hooks). The compiler lowers <code>fmt.format(f"...")</code>, <code>str_buffer.append_format(f"...")</code>, and <code>string.String.append_format(f"...")</code> directly to the formatted output without an intermediate allocation.</p>
1897
1897
 
@@ -2242,7 +2242,7 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
2242
2242
  <tr><td><code>std.gzip</code> <code>std.tar</code></td><td>Compression</td></tr>
2243
2243
  <tr><td><code>std.sync</code> <code>std.thread</code> <code>std.jobs</code></td><td>Concurrency</td></tr>
2244
2244
  <tr><td><code>std.fsm</code> <code>std.goap</code> <code>std.behavior_tree</code></td><td>AI / State machines</td></tr>
2245
- <tr><td><code>std.cell</code></td><td>Shared mutable cell allocation</td></tr>
2245
+ <tr><td><code>std.box</code></td><td>Explicit single-value heap storage for shared mutable state</td></tr>
2246
2246
  </table>
2247
2247
  </div>
2248
2248
  </section>
@@ -28,7 +28,7 @@ The output target is beautiful C. The generated C should be readable enough that
28
28
  - No macro system that rewrites arbitrary ASTs
29
29
  - No garbage collector
30
30
  - No implicit conversions between unrelated primitive types
31
- - No user-invisible allocation for strings, collections, ordinary values, or method calls. Capturing a `proc` may allocate a ref-counted closure environment as part of proc value semantics; owned text and other storage use explicit allocating surfaces.
31
+ - No user-invisible allocation for collections, ordinary values, or method calls. Capturing a `proc` may allocate a ref-counted closure environment as part of proc value semantics. Dynamic format strings are the one text case that builds a heap-backed temporary, released after use (see the text-construction rule in §1); other owned text and storage use explicit allocating surfaces.
32
32
 
33
33
  ## Design rules
34
34
 
@@ -38,7 +38,7 @@ If code allocates, takes an address, dereferences a raw pointer, performs an FFI
38
38
 
39
39
  FFI visibility belongs at the declaration site. Raw `external` files expose exact ABI types. Imported foreign declarations may project those raw types into ordinary Milk Tea types, but the projection rule, temporary-storage rule, and ownership rule must be declared there instead of repeated at every call site.
40
40
 
41
- The same rule applies to text construction. Plain string literals and format string literals are borrowed `str` values. The `+` operator on `str` allocates a new heap-backed `str` — the one everyday convenience that does allocate. For loops or amortized building, `string.String` and `str_buffer[N]` remain the explicit surfaces with visible cost. Any other surface that builds owned text must say so explicitly, for example `std.fmt.format(f"...")` when ownership must escape.
41
+ The same rule applies to text construction. Plain string literals are borrowed `str` values. A dynamic format string `f"..."` builds into a heap-backed temporary that the compiler releases after the surrounding statement; returning it (or storing it in a `str` local) transfers ownership of that buffer to the caller. The `+` operator on `str` concatenates into a per-thread scratch buffer without allocating — the result is a borrowed `str` valid until that buffer's budget is exhausted. For loops or amortized building, and whenever ownership should stay explicit and controllable, `string.String` and `str_buffer[N]` remain the explicit surfaces with visible cost; `std.fmt.format(f"...")` is the explicit owned-text path returning a `string.String`.
42
42
 
43
43
  ### 2. C is the ABI ground truth
44
44
 
@@ -762,7 +762,7 @@ Built-in operators should match familiar C behavior where possible:
762
762
 
763
763
  No user-defined operator overloading.
764
764
 
765
- The `+` operator also concatenates two `str` values, allocating a new heap-backed string; `cstr` values are not concatenable. This is the one operator exception to the arithmetic rules, and it is described in the text-construction rule under Design rules §1.
765
+ The `+` operator also concatenates two `str` values into a per-thread scratch buffer, returning a borrowed `str`; `cstr` values are not concatenable. This is the one operator exception to the arithmetic rules, and it is described in the text-construction rule under Design rules §1.
766
766
 
767
767
  Built-in vector, matrix, and quaternion types support component-wise arithmetic with the standard operators:
768
768
 
@@ -1072,7 +1072,7 @@ Rules:
1072
1072
  11. `+`, `-`
1073
1073
  12. `*`, `/`, `%`
1074
1074
 
1075
- The `+` operator also concatenates two `str` operands, producing a new heap-backed `str` (§2.3). It does not concatenate `cstr` or mixed `str`/`cstr` operands.
1075
+ The `+` operator also concatenates two `str` operands into a per-thread scratch buffer, producing a borrowed `str` (no heap allocation) whose validity is bounded by the scratch buffer budget (§2.3). It does not concatenate `cstr` or mixed `str`/`cstr` operands.
1076
1076
 
1077
1077
  ### 5.4 Assignment operators
1078
1078
 
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.34"
6
+ VERSION = "0.3.37"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -22,8 +22,14 @@ module MilkTea
22
22
  "cstr"
23
23
  else
24
24
  pointee = pointer_candidate.sub(/\s*\*\z/, "")
25
- pointer_name = top_level_const_qualified?(pointee) ? "const_ptr" : "ptr"
26
- "#{pointer_name}[#{map_c_type(pointee, context:)}]"
25
+ if function_type_typedef?(pointee)
26
+ # `Name *` where Name is a function-type typedef is a function
27
+ # pointer, which is exactly what the `fn` type already is.
28
+ map_c_type(pointee, context:)
29
+ else
30
+ pointer_name = top_level_const_qualified?(pointee) ? "const_ptr" : "ptr"
31
+ "#{pointer_name}[#{map_c_type(pointee, context:)}]"
32
+ end
27
33
  end
28
34
  else
29
35
  unqualified = strip_qualifiers(normalized)
@@ -515,6 +521,10 @@ module MilkTea
515
521
  qual_type.end_with?("*")
516
522
  end
517
523
 
524
+ def function_type_typedef?(qual_type)
525
+ @function_type_typedef_names.include?(strip_qualifiers(qual_type))
526
+ end
527
+
518
528
  def c_string_pointer?(qual_type)
519
529
  pointee = qual_type.sub(/\s*\*\z/, "")
520
530
  unqualified = strip_qualifiers(pointee)
@@ -147,6 +147,11 @@ module MilkTea
147
147
  @visible_typedef_names = top_level_nodes.filter_map do |node|
148
148
  node["name"] if node["kind"] == "TypedefDecl" && allowed_declaration_name?(node["name"])
149
149
  end
150
+ @function_type_typedef_names = top_level_nodes.filter_map do |node|
151
+ if node["kind"] == "TypedefDecl" && allowed_declaration_name?(node["name"]) && extract_function_proto(node) && !function_pointer_type?(type_qual_type(node))
152
+ node["name"]
153
+ end
154
+ end
150
155
  build_alias_maps(top_level_nodes)
151
156
 
152
157
  declarations = []
@@ -139,6 +139,8 @@ module MilkTea
139
139
  end
140
140
  when Types::Task
141
141
  [task_type_name(type)]
142
+ when Types::Dyn
143
+ [dyn_type_name(type)]
142
144
  when Types::Proc
143
145
  [proc_type_name(type)]
144
146
  when Types::GenericInstance
@@ -153,6 +155,8 @@ module MilkTea
153
155
  end
154
156
  when Types::Function
155
157
  []
158
+ when Types::Tuple
159
+ [tuple_type_name(type)]
156
160
  when Types::Struct, Types::StructInstance, Types::Union, Types::Variant, Types::VariantInstance, Types::Event, Types::Subscription
157
161
  [named_type_c_name(type)]
158
162
  when Types::VariantArmPayload
@@ -347,7 +347,22 @@ module MilkTea
347
347
  def emit_address_of_operand(expression)
348
348
  return emit_expression(expression.operand) if expression.is_a?(IR::Unary) && expression.operator == "*"
349
349
 
350
- "&#{wrap_expression(expression)}"
350
+ return "&#{wrap_expression(expression)}" if c_expression_lvalue?(expression)
351
+
352
+ "&(#{c_type(expression.type)}[1]){ #{emit_expression(expression)} }[0]"
353
+ end
354
+
355
+ def c_expression_lvalue?(expression)
356
+ case expression
357
+ when IR::Name, IR::CheckedIndex, IR::CheckedSpanIndex, IR::AggregateLiteral, IR::ArrayLiteral, IR::VariantLiteral, IR::ZeroInit
358
+ true
359
+ when IR::Member, IR::Index
360
+ c_expression_lvalue?(expression.receiver)
361
+ when IR::Unary
362
+ expression.operator == "*"
363
+ else
364
+ false
365
+ end
351
366
  end
352
367
 
353
368
  def emit_cast_operand(expression)
@@ -464,7 +479,12 @@ module MilkTea
464
479
  def emit_addressof_field_initializer(field_type, value)
465
480
  c_type_name = named_type_c_name(field_type)
466
481
  inner = value.expression
467
- "((#{c_type_name}*)memcpy(malloc(sizeof(#{c_type_name})), &(#{emit_expression(inner)}), sizeof(#{c_type_name})))"
482
+ source = if c_expression_lvalue?(inner)
483
+ "&(#{emit_expression(inner)})"
484
+ else
485
+ "&(#{c_type(field_type)}[1]){ #{emit_expression(inner)} }[0]"
486
+ end
487
+ "((#{c_type_name}*)memcpy(malloc(sizeof(#{c_type_name})), #{source}, sizeof(#{c_type_name})))"
468
488
  end
469
489
 
470
490
  def emit_cyclic_array_initializer(field_type, value)
@@ -479,8 +499,10 @@ module MilkTea
479
499
  init = emit_initializer(value)
480
500
  source_expr = if init.start_with?("{")
481
501
  "&(#{c_type(field_type)})#{init}"
482
- else
502
+ elsif c_expression_lvalue?(value)
483
503
  "&(#{init})"
504
+ else
505
+ "&(#{c_type(field_type)}[1]){ #{init} }[0]"
484
506
  end
485
507
  "((#{field_c_name}*)memcpy(malloc(sizeof(#{field_c_name})), #{source_expr}, sizeof(#{field_c_name})))"
486
508
  end
@@ -104,7 +104,17 @@ module MilkTea
104
104
  def emit_struct_equality_helpers
105
105
  struct_decls_by_linkage = (emitted_aggregate_structs + collect_generic_struct_decls).each_with_object({}) { |decl, map| map[decl.linkage_name] = decl }
106
106
  struct_equality_types
107
- .filter_map { |type| type.is_a?(Types::VariantArmPayload) ? type : struct_decls_by_linkage[named_type_c_name(type)] }
107
+ .filter_map do |type|
108
+ if type.is_a?(Types::VariantArmPayload)
109
+ type
110
+ elsif struct_decls_by_linkage.key?(named_type_c_name(type))
111
+ struct_decls_by_linkage[named_type_c_name(type)]
112
+ elsif type.is_a?(Types::Struct)
113
+ # External structs (raw ABI bindings) are not lowered into
114
+ # @program.structs, but their field layout is still known.
115
+ type
116
+ end
117
+ end
108
118
  .flat_map { |decl_or_type| emit_struct_equality_helper(decl_or_type) }
109
119
  end
110
120
 
@@ -112,11 +122,14 @@ module MilkTea
112
122
  if struct_decl_or_type.is_a?(IR::StructDecl)
113
123
  outer_c = struct_decl_or_type.linkage_name
114
124
  fields = struct_decl_or_type.fields
115
- else
125
+ elsif struct_decl_or_type.is_a?(Types::VariantArmPayload)
116
126
  payload = struct_decl_or_type
117
127
  outer_c = named_type_c_name(payload)
118
128
  arm_fields = payload.variant_type.arm(payload.arm_name) || {}
119
129
  fields = arm_fields.map { |name, field_type| IR::Field.new(name:, type: field_type) }
130
+ else
131
+ outer_c = named_type_c_name(struct_decl_or_type)
132
+ fields = struct_decl_or_type.fields.map { |name, field_type| IR::Field.new(name:, type: field_type) }
120
133
  end
121
134
 
122
135
  lines = ["static bool mt_struct_eq_#{outer_c}(struct #{outer_c} left, struct #{outer_c} right) {"]
@@ -266,6 +266,10 @@ module MilkTea
266
266
  simd_types = []
267
267
  visited = {}
268
268
 
269
+ all_emitted_top_level_values.each do |value|
270
+ collect_simd_type(value.type, simd_types, visited)
271
+ end
272
+
269
273
  emitted_functions.each do |function|
270
274
  collect_simd_type(function.return_type, simd_types, visited)
271
275
  function.params.each do |param|
@@ -280,6 +284,16 @@ module MilkTea
280
284
  end
281
285
  end
282
286
 
287
+ @program.unions.each do |union_decl|
288
+ union_decl.fields.each do |field|
289
+ collect_simd_type(field.type, simd_types, visited)
290
+ end
291
+ end
292
+
293
+ each_variant_arm_field_type do |field_type|
294
+ collect_simd_type(field_type, simd_types, visited)
295
+ end
296
+
283
297
  simd_types.uniq
284
298
  end
285
299
 
@@ -299,9 +313,32 @@ module MilkTea
299
313
  return unless type
300
314
  return if visited[type]
301
315
 
302
- if type.is_a?(Types::Simd)
316
+ visited[type] = true
317
+
318
+ case type
319
+ when Types::Simd
303
320
  simd_types << type
304
- visited[type] = true
321
+ when Types::Nullable
322
+ collect_simd_type(type.base, simd_types, visited)
323
+ when Types::GenericInstance
324
+ type.arguments.each do |argument|
325
+ collect_simd_type(argument, simd_types, visited) unless argument.is_a?(Types::LiteralTypeArg)
326
+ end
327
+ when Types::Function
328
+ type.params.each do |param|
329
+ collect_simd_type(param.type, simd_types, visited)
330
+ end
331
+ collect_simd_type(type.return_type, simd_types, visited)
332
+ when Types::Struct, Types::Union
333
+ type.fields.each_value do |field_type|
334
+ collect_simd_type(field_type, simd_types, visited)
335
+ end
336
+ when Types::Variant
337
+ type.arm_names.each do |arm_name|
338
+ type.arm(arm_name).each_value do |field_type|
339
+ collect_simd_type(field_type, simd_types, visited)
340
+ end
341
+ end
305
342
  end
306
343
  end
307
344
 
@@ -78,6 +78,10 @@ module MilkTea
78
78
  nil
79
79
  when AST::ExpressionList
80
80
  expression.elements.filter_map { |element| evaluate(element) }
81
+ when AST::RangeExpr
82
+ start_val = evaluate(expression.start_expr)
83
+ end_val = evaluate(expression.end_expr)
84
+ start_val.is_a?(Integer) && end_val.is_a?(Integer) ? (start_val...end_val).to_a : nil
81
85
  when AST::IntegerLiteral, AST::FloatLiteral, AST::BooleanLiteral
82
86
  expression.value
83
87
  when AST::StringLiteral
@@ -184,6 +188,8 @@ module MilkTea
184
188
  right = evaluate(expression.right)
185
189
 
186
190
  case expression.operator
191
+ when ".."
192
+ left.is_a?(Integer) && right.is_a?(Integer) ? (left...right).to_a : nil
187
193
  when "=="
188
194
  CompileTime.equality_result(left, right)
189
195
  when "!="
@@ -243,35 +249,41 @@ module MilkTea
243
249
  result = nil
244
250
 
245
251
  statements.each do |statement|
246
- case statement
247
- when AST::LocalDecl
248
- result = evaluate_local_decl(statement, scopes:)
249
- when AST::ReturnStmt
250
- value = statement.value ? evaluate_expression(statement.value, scopes:) : nil
251
- raise ReturnValue.new(value)
252
- when AST::WhileStmt
253
- result = evaluate_while(statement, scopes:)
254
- when AST::ForStmt
255
- result = evaluate_for(statement, scopes:)
256
- when AST::Assignment
257
- result = evaluate_assignment(statement, scopes:)
258
- when AST::IfStmt
259
- result = evaluate_if(statement, scopes:)
260
- when AST::ExpressionStmt
261
- evaluate_expression(statement.expression, scopes:)
262
- when AST::PassStmt, AST::BreakStmt, AST::ContinueStmt
263
- # no-op at compile time
264
- when AST::EmitStmt
265
- # evaluated during lowering
266
- result = nil
267
- else
268
- result = nil
269
- end
252
+ result = evaluate_statement(statement, scopes:)
270
253
  end
271
254
 
272
255
  result
273
256
  end
274
257
 
258
+ def evaluate_statement(statement, scopes:)
259
+ case statement
260
+ when AST::LocalDecl
261
+ evaluate_local_decl(statement, scopes:)
262
+ when AST::ReturnStmt
263
+ value = statement.value ? evaluate_expression(statement.value, scopes:) : nil
264
+ raise ReturnValue.new(value)
265
+ when AST::WhileStmt
266
+ evaluate_while(statement, scopes:)
267
+ when AST::ForStmt
268
+ evaluate_for(statement, scopes:)
269
+ when AST::MatchStmt
270
+ evaluate_match(statement, scopes:)
271
+ when AST::Assignment
272
+ evaluate_assignment(statement, scopes:)
273
+ when AST::IfStmt
274
+ evaluate_if(statement, scopes:)
275
+ when AST::ExpressionStmt
276
+ evaluate_expression(statement.expression, scopes:)
277
+ when AST::PassStmt, AST::BreakStmt, AST::ContinueStmt
278
+ # no-op at compile time
279
+ when AST::EmitStmt
280
+ # emitted declarations are collected during lowering
281
+ nil
282
+ else
283
+ nil
284
+ end
285
+ end
286
+
275
287
  def evaluate_expression(expression, scopes:)
276
288
  case expression
277
289
  when AST::Identifier
@@ -304,11 +316,34 @@ module MilkTea
304
316
  value = evaluate_expression(assignment.value, scopes:)
305
317
  case assignment.target
306
318
  when AST::Identifier
319
+ if assignment.operator != "="
320
+ current = @variables[assignment.target.name]
321
+ value = apply_compile_time_binary(assignment.operator.chomp("="), current, value)
322
+ end
307
323
  @variables[assignment.target.name] = value
308
324
  end
309
325
  value
310
326
  end
311
327
 
328
+ def apply_compile_time_binary(operator, left, right)
329
+ case operator
330
+ when "+" then left.is_a?(Numeric) && right.is_a?(Numeric) ? left + right : nil
331
+ when "-" then left.is_a?(Numeric) && right.is_a?(Numeric) ? left - right : nil
332
+ when "*" then left.is_a?(Numeric) && right.is_a?(Numeric) ? left * right : nil
333
+ when "/" then left.is_a?(Numeric) && right.is_a?(Numeric) && !zero_numeric?(right) ? left / right : nil
334
+ when "%" then left.is_a?(Integer) && right.is_a?(Integer) && !right.zero? ? left % right : nil
335
+ when "&" then left.is_a?(Integer) && right.is_a?(Integer) ? left & right : nil
336
+ when "|" then left.is_a?(Integer) && right.is_a?(Integer) ? left | right : nil
337
+ when "^" then left.is_a?(Integer) && right.is_a?(Integer) ? left ^ right : nil
338
+ when "<<" then left.is_a?(Integer) && right.is_a?(Integer) ? left << right : nil
339
+ when ">>" then left.is_a?(Integer) && right.is_a?(Integer) ? left >> right : nil
340
+ end
341
+ end
342
+
343
+ def zero_numeric?(value)
344
+ (value.is_a?(Integer) && value.zero?) || (value.is_a?(Float) && value.zero?)
345
+ end
346
+
312
347
  def evaluate_while(statement, scopes:)
313
348
  result = nil
314
349
  iterations = 0
@@ -319,17 +354,7 @@ module MilkTea
319
354
  break unless condition
320
355
  break unless CompileTime.boolean_value?(condition)
321
356
 
322
- statement.body.each do |body_stmt|
323
- case body_stmt
324
- when AST::ReturnStmt
325
- value = body_stmt.value ? evaluate_expression(body_stmt.value, scopes:) : nil
326
- raise ReturnValue.new(value)
327
- when AST::Assignment
328
- evaluate_assignment(body_stmt, scopes:)
329
- when AST::ExpressionStmt
330
- evaluate_expression(body_stmt.expression, scopes:)
331
- end
332
- end
357
+ statement.body.each { |body_stmt| evaluate_statement(body_stmt, scopes:) }
333
358
  iterations += 1
334
359
  end
335
360
 
@@ -347,21 +372,7 @@ module MilkTea
347
372
 
348
373
  iterable.each do |element|
349
374
  @variables[loop_var_name] = element
350
- statement.body.each do |body_stmt|
351
- case body_stmt
352
- when AST::ReturnStmt
353
- value = body_stmt.value ? evaluate_expression(body_stmt.value, scopes:) : nil
354
- raise ReturnValue.new(value)
355
- when AST::Assignment
356
- evaluate_assignment(body_stmt, scopes:)
357
- when AST::ExpressionStmt
358
- evaluate_expression(body_stmt.expression, scopes:)
359
- when AST::IfStmt
360
- result = evaluate_if(body_stmt, scopes:)
361
- when AST::WhileStmt
362
- result = evaluate_while(body_stmt, scopes:)
363
- end
364
- end
375
+ statement.body.each { |body_stmt| evaluate_statement(body_stmt, scopes:) }
365
376
  end
366
377
 
367
378
  result
@@ -371,32 +382,27 @@ module MilkTea
371
382
  statement.branches.each do |branch|
372
383
  condition = evaluate_expression(branch.condition, scopes:)
373
384
  if CompileTime.boolean_value?(condition) && condition
374
- branch.body.each do |body_stmt|
375
- case body_stmt
376
- when AST::ReturnStmt
377
- value = body_stmt.value ? evaluate_expression(body_stmt.value, scopes:) : nil
378
- raise ReturnValue.new(value)
379
- when AST::Assignment
380
- evaluate_assignment(body_stmt, scopes:)
381
- when AST::ExpressionStmt
382
- evaluate_expression(body_stmt.expression, scopes:)
383
- end
384
- end
385
+ branch.body.each { |body_stmt| evaluate_statement(body_stmt, scopes:) }
385
386
  return condition
386
387
  end
387
388
  end
388
389
 
389
390
  if statement.else_body
390
- statement.else_body.each do |body_stmt|
391
- case body_stmt
392
- when AST::ReturnStmt
393
- value = body_stmt.value ? evaluate_expression(body_stmt.value, scopes:) : nil
394
- raise ReturnValue.new(value)
395
- when AST::Assignment
396
- evaluate_assignment(body_stmt, scopes:)
397
- when AST::ExpressionStmt
398
- evaluate_expression(body_stmt.expression, scopes:)
399
- end
391
+ statement.else_body.each { |body_stmt| evaluate_statement(body_stmt, scopes:) }
392
+ end
393
+
394
+ nil
395
+ end
396
+
397
+ def evaluate_match(statement, scopes:)
398
+ scrutinee = evaluate_expression(statement.expression, scopes:)
399
+ return nil unless scrutinee
400
+
401
+ statement.arms.each do |arm|
402
+ wildcard = arm.pattern.is_a?(AST::Identifier) && arm.pattern.name == "_"
403
+ if wildcard || CompileTime.equality_result(scrutinee, evaluate_expression(arm.pattern, scopes:)) == true
404
+ arm.body.each { |body_stmt| evaluate_statement(body_stmt, scopes:) }
405
+ return scrutinee
400
406
  end
401
407
  end
402
408
 
@@ -506,7 +512,7 @@ module MilkTea
506
512
  end
507
513
 
508
514
  def self.core_member_handles(type)
509
- type.members.map { |name, value| Types::MemberHandle.new(nil, name, value) }
515
+ type.members.map { |name| Types::MemberHandle.new(nil, name, type.member_value(name)) }
510
516
  end
511
517
 
512
518
  def self.core_evaluate_type_returning(
@@ -186,6 +186,7 @@ module MilkTea
186
186
  mutable: false,
187
187
  pointer: false,
188
188
  const_value: element,
189
+ substitute_const_value: true,
189
190
  )
190
191
  emit_stmts, other_stmts = statement.body.partition { |s| s.is_a?(AST::EmitStmt) }
191
192
  emit_stmts.each do |emit_stmt|
@@ -486,13 +487,11 @@ module MilkTea
486
487
  expected_type: return_type,
487
488
  contextual_int_to_float: contextual_int_to_float_target?(return_type),
488
489
  ) : nil
489
- if prepared_cleanups.any? && cstr_trackable_type?(return_type)
490
- raise LoweringError.new("formatted string temporaries cannot be returned as borrowed text; use std.fmt.format(f\"...\") when ownership must escape",
491
- line: statement.line, column: statement.column, path: @ctx.current_analysis_path)
492
- end
493
-
494
490
  prepared_cleanup_list = prepared_cleanups.flat_map(&:itself)
495
- if prepared_cleanup_list.any? && return_type.is_a?(Types::Struct) && struct_contains_string_field?(return_type)
491
+ if prepared_cleanup_list.any? && (cstr_trackable_type?(return_type) ||
492
+ (return_type.is_a?(Types::Struct) && struct_contains_string_field?(return_type)))
493
+ # A dynamic f-string temp owns its heap buffer; returning it transfers
494
+ # ownership to the caller, so the format release must not run.
496
495
  prepared_cleanup_list = prepared_cleanup_list.reject { |stmt| stmt.is_a?(IR::ExpressionStmt) && stmt.expression.is_a?(IR::Call) && stmt.expression.callee == "mt_format_str_release" }
497
496
  end
498
497
  cleanup = prepared_cleanup_list + cleanup_statements(local_defers, active_defers)
@@ -793,7 +792,7 @@ module MilkTea
793
792
  else_body: nil,
794
793
  )
795
794
  end
796
- local_defers.concat(prepared_cleanups)
795
+ local_defers.concat(reject_format_releases_for_assignment(prepared_cleanups, storage_type))
797
796
  if contains_proc_storage_type?(storage_type)
798
797
  local_value = IR::Name.new(name: linkage_name, type: storage_type, pointer: false)
799
798
  local_defers << lower_proc_nullable_release_statements(local_value, storage_type)
@@ -23,6 +23,7 @@ module MilkTea
23
23
  expanded_declarations.grep(AST::ConstDecl).filter_map do |decl|
24
24
  type = @ctx.values.fetch(decl.name).type
25
25
  const_value = @ctx.values.fetch(decl.name).const_value
26
+ ensure_registered_storage_type_types(type)
26
27
 
27
28
  next if type == Types::BUILTIN_TYPE_META_TYPE
28
29
 
@@ -35,7 +36,7 @@ module MilkTea
35
36
  value = lower_const_value_literal(type, const_value)
36
37
  end
37
38
  elsif decl.block_body || decl.value.is_a?(AST::ExpressionList)
38
- raise LoweringError.new("constant #{decl.name} has no compile-time value", line: decl.line, column: decl.column)
39
+ raise LoweringError.new("constant #{decl.name} has no compile-time value", line: decl.line, column: decl.column, path: @ctx.current_analysis_path)
39
40
  else
40
41
  value = lower_static_storage_initializer(decl.value, env: empty_env, expected_type: type)
41
42
  if (decl.value.is_a?(AST::Call) || decl.value.is_a?(AST::Specialization)) && static_initializer_ir_has_call?(value)
@@ -78,6 +79,43 @@ module MilkTea
78
79
  end
79
80
  end
80
81
 
82
+ ## Register C struct typedefs for every tuple type reachable from a
83
+ ## module-storage type (consts and globals). `ensure_tuple_struct` is
84
+ ## normally called while lowering tuple *expressions*, so a tuple that
85
+ ## only appears in static storage never gets its `mt_tuple_...` typedef
86
+ ## emitted; the C compiler then fails with "unknown type name".
87
+ def ensure_registered_storage_type_types(type, seen = {})
88
+ return if type.nil? || seen[type]
89
+
90
+ seen[type] = true
91
+ if type.is_a?(Types::Tuple)
92
+ ensure_tuple_struct(type)
93
+ type.element_types.each { |element_type| ensure_registered_storage_type_types(element_type, seen) }
94
+ end
95
+
96
+ case type
97
+ when Types::Nullable
98
+ ensure_registered_storage_type_types(type.base, seen)
99
+ when Types::Span
100
+ ensure_registered_storage_type_types(type.element_type, seen)
101
+ when Types::Task
102
+ ensure_registered_storage_type_types(type.result_type, seen)
103
+ when Types::GenericInstance, Types::StructInstance, Types::VariantInstance
104
+ type.arguments.each do |argument|
105
+ ensure_registered_storage_type_types(argument, seen) unless argument.is_a?(Types::LiteralTypeArg)
106
+ end
107
+ end
108
+
109
+ if type.respond_to?(:fields) && type.fields
110
+ type.fields.each_value { |field_type| ensure_registered_storage_type_types(field_type, seen) }
111
+ end
112
+ if type.respond_to?(:arms)
113
+ type.arms.each_value do |arm_fields|
114
+ arm_fields.each_value { |field_type| ensure_registered_storage_type_types(field_type, seen) }
115
+ end
116
+ end
117
+ end
118
+
81
119
  def lower_const_value_literal(type, const_value)
82
120
  case const_value
83
121
  when Integer
@@ -130,6 +168,7 @@ module MilkTea
130
168
  next unless decl.is_a?(AST::VarDecl) || decl.is_a?(AST::EventDecl)
131
169
 
132
170
  type = @ctx.values.fetch(decl.name).type
171
+ ensure_registered_storage_type_types(type)
133
172
  ensure_event_runtime(type) if type.is_a?(Types::Event)
134
173
  value = if decl.is_a?(AST::VarDecl) && decl.value
135
174
  lower_static_storage_initializer(decl.value, env: empty_env, expected_type: type)
@@ -643,6 +643,8 @@ module MilkTea
643
643
  end
644
644
  end
645
645
 
646
+ written_scalar_captures = captures.select { |c| written_names.include?(c.name) && !array_capture_names.include?(c.name) }
647
+
646
648
  @artifacts.synthetic_structs << IR::StructDecl.new(
647
649
  name: cap_struct_c_name, linkage_name: cap_struct_c_name,
648
650
  fields: cap_fields, packed: false, alignment: nil,
@@ -674,6 +676,14 @@ module MilkTea
674
676
 
675
677
  rewritten_body = array_capture_names.empty? ? block_body : rewrite_pfor_array_captures(block_body, array_capture_names)
676
678
  worker_body.concat(rewritten_body)
679
+
680
+ written_scalar_captures.each do |c|
681
+ worker_body << IR::Assignment.new(
682
+ target: IR::Member.new(receiver: cap_name_ir, member: c.name, type: c.type),
683
+ operator: "=",
684
+ value: IR::Name.new(name: c.name, type: c.type, pointer: false),
685
+ )
686
+ end
677
687
  end
678
688
 
679
689
  @artifacts.synthetic_functions << IR::Function.new(
@@ -715,7 +725,7 @@ module MilkTea
715
725
  )
716
726
  end
717
727
 
718
- { worker_c_name:, cap_local_name:, cap_struct_type:, cap_init:, capture_names: Set.new(captures.map(&:name)), written_names:, captureless: }
728
+ { worker_c_name:, cap_local_name:, cap_struct_type:, cap_init:, capture_names: Set.new(captures.map(&:name)), written_names:, written_scalar_captures: captureless ? [] : written_scalar_captures, captureless: }
719
729
  end
720
730
 
721
731
  validate_pfor_write_conflicts!(block_infos)
@@ -768,6 +778,20 @@ module MilkTea
768
778
  type: void_type,
769
779
  ))
770
780
 
781
+ block_infos.each do |info|
782
+ info[:written_scalar_captures].each do |c|
783
+ call_site << IR::Assignment.new(
784
+ target: IR::Name.new(name: c.name, type: c.type, pointer: false),
785
+ operator: "=",
786
+ value: IR::Member.new(
787
+ receiver: IR::Name.new(name: info[:cap_local_name], type: info[:cap_struct_type], pointer: false),
788
+ member: c.name,
789
+ type: c.type,
790
+ ),
791
+ )
792
+ end
793
+ end
794
+
771
795
  IR::BlockStmt.new(body: call_site)
772
796
  end
773
797
 
@@ -745,7 +745,7 @@ module MilkTea
745
745
  end
746
746
  return function_type_for_name(expression.name) if @ctx.functions.key?(expression.name)
747
747
 
748
- raise LoweringError.new("unknown identifier #{expression.name}", line: expression.line, column: expression.column)
748
+ raise LoweringError.new("unknown identifier #{expression.name}", line: expression.line, column: expression.column, path: @ctx.current_analysis_path)
749
749
  when AST::MemberAccess
750
750
  if (type_expr = resolve_type_expression(expression.receiver))
751
751
  member_type = resolve_type_member(type_expr, expression.member)
@@ -784,7 +784,7 @@ module MilkTea
784
784
  end
785
785
 
786
786
  return receiver_type.field(expression.member) if receiver_type.respond_to?(:field)
787
- raise LoweringError.new("unknown member #{expression.member}", line: expression.line, column: expression.column)
787
+ raise LoweringError.new("unknown member #{expression.member}", line: expression.line, column: expression.column, path: @ctx.current_analysis_path)
788
788
  when AST::IndexAccess
789
789
  receiver_type = infer_expression_type(expression.receiver, env:)
790
790
  index_type = infer_expression_type(expression.index, env:)
@@ -1677,7 +1677,15 @@ module MilkTea
1677
1677
  evaluate_attribute_arg_call(expression.arguments, env:)
1678
1678
  else
1679
1679
  callee_name = expression.callee.callee.is_a?(AST::Identifier) ? expression.callee.callee.name : nil
1680
- if callee_name
1680
+ if callee_name == "array"
1681
+ values = []
1682
+ expression.arguments.each do |argument|
1683
+ val = compile_time_const_value(argument.value, env:)
1684
+ return nil unless val
1685
+ values << val
1686
+ end
1687
+ values
1688
+ elsif callee_name
1681
1689
  func = @ctx.functions[callee_name]
1682
1690
  if func&.ast&.respond_to?(:const) && func.ast.const
1683
1691
  evaluate_const_function_body_lower(func, expression.arguments)
@@ -459,8 +459,8 @@ module MilkTea
459
459
  end
460
460
  end
461
461
 
462
- def local_binding(type:, linkage_name:, mutable:, pointer:, storage_type: nil, projection: nil, cstr_backed: false, cstr_list_backed: false, const_value: nil)
463
- { type:, storage_type: storage_type || type, linkage_name:, mutable:, pointer:, projection:, cstr_backed:, cstr_list_backed:, const_value: }
462
+ def local_binding(type:, linkage_name:, mutable:, pointer:, storage_type: nil, projection: nil, cstr_backed: false, cstr_list_backed: false, const_value: nil, substitute_const_value: false)
463
+ { type:, storage_type: storage_type || type, linkage_name:, mutable:, pointer:, projection:, cstr_backed:, cstr_list_backed:, const_value:, substitute_const_value: }
464
464
  end
465
465
 
466
466
  def callable_type?(type)
@@ -904,6 +904,10 @@ module MilkTea
904
904
  visible_type = binding[:type]
905
905
  projection = binding[:projection]
906
906
 
907
+ if binding[:substitute_const_value] && !binding[:const_value].nil?
908
+ return lower_const_value_literal(visible_type, binding[:const_value])
909
+ end
910
+
907
911
  if projection == :result_success_value
908
912
  local_ref = IR::Name.new(name: binding[:linkage_name], type: storage_type, pointer: binding[:pointer])
909
913
  return variant_binding_projection_expression(local_ref, storage_type, "success", "value", visible_type)
@@ -297,7 +297,7 @@ module MilkTea
297
297
  expression = nil
298
298
  arms = []
299
299
  expression = parse_expression
300
- arms = parse_match_arms(arms)
300
+ arms = normalize_mixed_match_arms(parse_match_arms(arms))
301
301
  if arms.first&.is_a?(AST::MatchExprArm)
302
302
  expr = AST::MatchExpr.new(expression:, arms:, line:, column: token.column, length: token.lexeme.length)
303
303
  AST::ExpressionStmt.new(expression: expr, line:)
@@ -311,12 +311,13 @@ module MilkTea
311
311
  recovered_arms = synchronize_to_match_arm_boundary
312
312
  target_line = line
313
313
  if recovered_arms
314
+ combined_arms = normalize_mixed_match_arms(arms + recovered_arms)
314
315
  stmt =
315
- if recovered_arms.first&.is_a?(AST::MatchExprArm)
316
- expr = AST::MatchExpr.new(expression: expression || recovery_error_expr(e), arms: arms + recovered_arms, line: target_line, column: token.column, length: token.lexeme.length)
316
+ if combined_arms.first&.is_a?(AST::MatchExprArm)
317
+ expr = AST::MatchExpr.new(expression: expression || recovery_error_expr(e), arms: combined_arms, line: target_line, column: token.column, length: token.lexeme.length)
317
318
  AST::ExpressionStmt.new(expression: expr, line: target_line)
318
319
  else
319
- AST::MatchStmt.new(expression: expression || recovery_error_expr(e), arms: arms + recovered_arms, line: target_line, column: token.column, length: token.lexeme.length)
320
+ AST::MatchStmt.new(expression: expression || recovery_error_expr(e), arms: combined_arms, line: target_line, column: token.column, length: token.lexeme.length)
320
321
  end
321
322
  return stmt
322
323
  end
@@ -335,6 +336,33 @@ module MilkTea
335
336
  arms
336
337
  end
337
338
 
339
+ ## A statement match may mix inline value arms (`pattern: expr`) with
340
+ ## block arms. The inline form is a single expression statement, so when
341
+ ## any arm is a block the inline arms are rewritten to block arms with a
342
+ ## single expression-statement body. This keeps MatchStmt arms
343
+ ## homogeneous (all MatchArm) and MatchExpr arms homogeneous (all
344
+ ## MatchExprArm); the sema / control-flow / lowering consumers rely on
345
+ ## that invariant.
346
+ def normalize_mixed_match_arms(arms)
347
+ return arms unless arms.any? { |arm| arm.is_a?(AST::MatchExprArm) } && arms.any? { |arm| arm.is_a?(AST::MatchArm) }
348
+
349
+ arms.map do |arm|
350
+ if arm.is_a?(AST::MatchExprArm)
351
+ AST::MatchArm.new(
352
+ pattern: arm.pattern,
353
+ binding_name: arm.binding_name,
354
+ binding_line: arm.binding_line,
355
+ binding_column: arm.binding_column,
356
+ body: [AST::ExpressionStmt.new(expression: arm.value, line: arm.line)],
357
+ line: arm.line,
358
+ column: arm.column,
359
+ )
360
+ else
361
+ arm
362
+ end
363
+ end
364
+ end
365
+
338
366
  def parse_match_arm_body(arms = [])
339
367
  skip_newlines
340
368
  until check(:dedent) || eof?
@@ -652,7 +680,7 @@ module MilkTea
652
680
  token = previous
653
681
  line = token.line
654
682
  discriminant = parse_expression
655
- branches = parse_match_arms([])
683
+ branches = normalize_mixed_match_arms(parse_match_arms([]))
656
684
  else_body = if check(:else)
657
685
  if check_next(:newline) || check_next(:indent)
658
686
  parse_else_branch_body
@@ -734,14 +762,14 @@ module MilkTea
734
762
  line = token.line
735
763
  arms = []
736
764
  expression = parse_expression
737
- arms = parse_match_arms(arms)
765
+ arms = normalize_mixed_match_arms(parse_match_arms(arms))
738
766
  AST::MatchStmt.new(expression:, arms:, inline: true, line:, column: token.column, length: token.lexeme.length)
739
767
  rescue ParseError => e
740
768
  raise unless @recovery_errors
741
769
 
742
770
  @recovery_errors << e
743
771
  recovered_arms = synchronize_to_match_arm_boundary
744
- return AST::MatchStmt.new(expression: expression || recovery_error_expr(e), arms: arms + recovered_arms, inline: true, line:, column: token.column, length: token.lexeme.length) if recovered_arms
772
+ return AST::MatchStmt.new(expression: expression || recovery_error_expr(e), arms: normalize_mixed_match_arms(arms + recovered_arms), inline: true, line:, column: token.column, length: token.lexeme.length) if recovered_arms
745
773
 
746
774
  recovery_error_stmt(e)
747
775
  end
@@ -211,7 +211,8 @@ module MilkTea
211
211
  when IR::NullableSpanIndex
212
212
  "nullable_span_index<#{expression.receiver_type}>(#{render_expression(expression.receiver)}, #{render_expression(expression.index)})"
213
213
  when IR::Call
214
- wrap("#{expression.callee}(#{expression.arguments.map { |argument| render_expression(argument) }.join(', ')})", parent_precedence, POSTFIX_PRECEDENCE)
214
+ callee_text = expression.callee.is_a?(String) ? expression.callee : render_expression(expression.callee)
215
+ wrap("#{callee_text}(#{expression.arguments.map { |argument| render_expression(argument) }.join(', ')})", parent_precedence, POSTFIX_PRECEDENCE)
215
216
  when IR::Unary
216
217
  operand = render_expression(expression.operand, UNARY_PRECEDENCE)
217
218
  text = expression.operator == "not" ? "not #{operand}" : "#{expression.operator}#{operand}"
@@ -258,6 +259,8 @@ module MilkTea
258
259
  end
259
260
  when IR::ArrayLiteral
260
261
  "#{expression.type}(#{expression.elements.map { |element| render_expression(element) }.join(', ')})"
262
+ when IR::SimdLaneWith
263
+ "#{render_expression(expression.src)}.with(#{render_expression(expression.index)}, #{render_expression(expression.value)})"
261
264
  when IR::Assignment
262
265
  "#{render_expression(expression.target)} #{expression.operator} #{render_expression(expression.value)}"
263
266
  else
@@ -101,11 +101,21 @@ module MilkTea
101
101
 
102
102
  def find_method_by_receiver_name(module_binding, receiver_type, name)
103
103
  module_binding.methods.each do |key, methods|
104
- return methods[name] if key.is_a?(receiver_type.class) && key.name == receiver_type.name && methods.key?(name)
104
+ next unless key.is_a?(receiver_type.class)
105
+ next unless key.name == receiver_type.name
106
+ next unless same_type_module?(key, receiver_type)
107
+ return methods[name] if methods.key?(name)
105
108
  end
106
109
  nil
107
110
  end
108
111
 
112
+ def same_type_module?(type_a, type_b)
113
+ module_a = receiver_type_module_name(type_a)
114
+ module_b = receiver_type_module_name(type_b)
115
+ return true if module_a.nil? || module_b.nil?
116
+ module_a == module_b
117
+ end
118
+
109
119
  def reachable_module_binding_for_type(receiver_type)
110
120
  module_name = receiver_type_module_name(receiver_type)
111
121
  return nil unless module_name
@@ -709,7 +719,7 @@ module MilkTea
709
719
 
710
720
  def sized_layout_type?(type)
711
721
  case type
712
- when Types::Primitive, Types::Struct, Types::StructInstance, Types::Union, Types::Enum, Types::Flags, Types::Variant, Types::Span, Types::StringView, Types::Task, Types::Event, Types::Subscription
722
+ when Types::Primitive, Types::Struct, Types::StructInstance, Types::Union, Types::Enum, Types::Flags, Types::Variant, Types::Tuple, Types::Span, Types::StringView, Types::Task, Types::Event, Types::Subscription
713
723
  true
714
724
  when Types::Nullable
715
725
  true
@@ -173,7 +173,7 @@ module MilkTea
173
173
 
174
174
  if (type_ref = type_ref_from_specialization(callee))
175
175
  specialized_type = resolve_type_ref(type_ref)
176
- return if specialized_type.is_a?(Types::Struct) || result_type?(specialized_type)
176
+ return if specialized_type.is_a?(Types::Struct) || task_type?(specialized_type) || specialized_type.is_a?(Types::Vector) || specialized_type.is_a?(Types::Matrix) || specialized_type.is_a?(Types::Quaternion) || specialized_type.is_a?(Types::Simd)
177
177
  end
178
178
  end
179
179
 
@@ -846,7 +846,10 @@ module MilkTea
846
846
  return false unless else_body
847
847
  text << else_body
848
848
 
849
- text.strip.length > 120
849
+ # The inline form would start at the `if` keyword's column, so the real
850
+ # line width includes the surrounding block's indentation.
851
+ indent = statement.branches.first.column.to_i - 1
852
+ (indent + text.strip.length) > 120
850
853
  end
851
854
 
852
855
  def source_line_from(line, column)
data/std/box2d.mt CHANGED
@@ -111,9 +111,9 @@ public const B2_PI: float = c.B2_PI
111
111
  public const B2_MAX_POLYGON_VERTICES: int = c.B2_MAX_POLYGON_VERTICES
112
112
  public const B2_DEFAULT_CATEGORY_BITS: int = c.B2_DEFAULT_CATEGORY_BITS
113
113
 
114
- public foreign function set_allocator(alloc_fcn: ptr[AllocFcn], free_fcn: ptr[FreeFcn]) -> void = c.b2SetAllocator
114
+ public foreign function set_allocator(alloc_fcn: AllocFcn, free_fcn: FreeFcn) -> void = c.b2SetAllocator
115
115
  public foreign function get_byte_count() -> int = c.b2GetByteCount
116
- public foreign function set_assert_fcn(assert_fcn: ptr[AssertFcn]) -> void = c.b2SetAssertFcn
116
+ public foreign function set_assert_fcn(assert_fcn: AssertFcn) -> void = c.b2SetAssertFcn
117
117
  public foreign function get_version() -> Version = c.b2GetVersion
118
118
  public foreign function internal_assert_fcn(condition: str as cstr, file_name: str as cstr, line_number: int) -> int = c.b2InternalAssertFcn
119
119
  public foreign function get_ticks() -> ulong = c.b2GetTicks
@@ -188,9 +188,9 @@ public foreign function dynamic_tree_move_proxy(tree: ptr[DynamicTree], proxy_id
188
188
  public foreign function dynamic_tree_enlarge_proxy(tree: ptr[DynamicTree], proxy_id: int, aabb: AABB) -> void = c.b2DynamicTree_EnlargeProxy
189
189
  public foreign function dynamic_tree_set_category_bits(tree: ptr[DynamicTree], proxy_id: int, category_bits: ptr_uint) -> void = c.b2DynamicTree_SetCategoryBits
190
190
  public foreign function dynamic_tree_get_category_bits(tree: ptr[DynamicTree], proxy_id: int) -> ulong = c.b2DynamicTree_GetCategoryBits
191
- public foreign function dynamic_tree_query(tree: const_ptr[DynamicTree], aabb: AABB, mask_bits: ptr_uint, callback: ptr[TreeQueryCallbackFcn], context: ptr[void]) -> TreeStats = c.b2DynamicTree_Query
192
- public foreign function dynamic_tree_ray_cast(tree: const_ptr[DynamicTree], input: const_ptr[RayCastInput], mask_bits: ptr_uint, callback: ptr[TreeRayCastCallbackFcn], context: ptr[void]) -> TreeStats = c.b2DynamicTree_RayCast
193
- public foreign function dynamic_tree_shape_cast(tree: const_ptr[DynamicTree], input: const_ptr[ShapeCastInput], mask_bits: ptr_uint, callback: ptr[TreeShapeCastCallbackFcn], context: ptr[void]) -> TreeStats = c.b2DynamicTree_ShapeCast
191
+ public foreign function dynamic_tree_query(tree: const_ptr[DynamicTree], aabb: AABB, mask_bits: ptr_uint, callback: TreeQueryCallbackFcn, context: ptr[void]) -> TreeStats = c.b2DynamicTree_Query
192
+ public foreign function dynamic_tree_ray_cast(tree: const_ptr[DynamicTree], input: const_ptr[RayCastInput], mask_bits: ptr_uint, callback: TreeRayCastCallbackFcn, context: ptr[void]) -> TreeStats = c.b2DynamicTree_RayCast
193
+ public foreign function dynamic_tree_shape_cast(tree: const_ptr[DynamicTree], input: const_ptr[ShapeCastInput], mask_bits: ptr_uint, callback: TreeShapeCastCallbackFcn, context: ptr[void]) -> TreeStats = c.b2DynamicTree_ShapeCast
194
194
  public foreign function dynamic_tree_get_height(tree: const_ptr[DynamicTree]) -> int = c.b2DynamicTree_GetHeight
195
195
  public foreign function dynamic_tree_get_area_ratio(tree: const_ptr[DynamicTree]) -> float = c.b2DynamicTree_GetAreaRatio
196
196
  public foreign function dynamic_tree_get_root_bounds(tree: const_ptr[DynamicTree]) -> AABB = c.b2DynamicTree_GetRootBounds
@@ -228,13 +228,13 @@ public foreign function world_draw(world_id: WorldId, inout draw: DebugDraw) ->
228
228
  public foreign function world_get_body_events(world_id: WorldId) -> BodyEvents = c.b2World_GetBodyEvents
229
229
  public foreign function world_get_sensor_events(world_id: WorldId) -> SensorEvents = c.b2World_GetSensorEvents
230
230
  public foreign function world_get_contact_events(world_id: WorldId) -> ContactEvents = c.b2World_GetContactEvents
231
- public foreign function world_overlap_aabb(world_id: WorldId, aabb: AABB, filter: QueryFilter, fcn: ptr[OverlapResultFcn], context: ptr[void]) -> TreeStats = c.b2World_OverlapAABB
232
- public foreign function world_overlap_shape(world_id: WorldId, proxy: const_ptr[ShapeProxy], filter: QueryFilter, fcn: ptr[OverlapResultFcn], context: ptr[void]) -> TreeStats = c.b2World_OverlapShape
233
- public foreign function world_cast_ray(world_id: WorldId, origin: Vec2, translation: Vec2, filter: QueryFilter, fcn: ptr[CastResultFcn], context: ptr[void]) -> TreeStats = c.b2World_CastRay
231
+ public foreign function world_overlap_aabb(world_id: WorldId, aabb: AABB, filter: QueryFilter, fcn: OverlapResultFcn, context: ptr[void]) -> TreeStats = c.b2World_OverlapAABB
232
+ public foreign function world_overlap_shape(world_id: WorldId, proxy: const_ptr[ShapeProxy], filter: QueryFilter, fcn: OverlapResultFcn, context: ptr[void]) -> TreeStats = c.b2World_OverlapShape
233
+ public foreign function world_cast_ray(world_id: WorldId, origin: Vec2, translation: Vec2, filter: QueryFilter, fcn: CastResultFcn, context: ptr[void]) -> TreeStats = c.b2World_CastRay
234
234
  public foreign function world_cast_ray_closest(world_id: WorldId, origin: Vec2, translation: Vec2, filter: QueryFilter) -> RayResult = c.b2World_CastRayClosest
235
- public foreign function world_cast_shape(world_id: WorldId, proxy: const_ptr[ShapeProxy], translation: Vec2, filter: QueryFilter, fcn: ptr[CastResultFcn], context: ptr[void]) -> TreeStats = c.b2World_CastShape
235
+ public foreign function world_cast_shape(world_id: WorldId, proxy: const_ptr[ShapeProxy], translation: Vec2, filter: QueryFilter, fcn: CastResultFcn, context: ptr[void]) -> TreeStats = c.b2World_CastShape
236
236
  public foreign function world_cast_mover(world_id: WorldId, mover: const_ptr[Capsule], translation: Vec2, filter: QueryFilter) -> float = c.b2World_CastMover
237
- public foreign function world_collide_mover(world_id: WorldId, mover: const_ptr[Capsule], filter: QueryFilter, fcn: ptr[PlaneResultFcn], context: ptr[void]) -> void = c.b2World_CollideMover
237
+ public foreign function world_collide_mover(world_id: WorldId, mover: const_ptr[Capsule], filter: QueryFilter, fcn: PlaneResultFcn, context: ptr[void]) -> void = c.b2World_CollideMover
238
238
  public foreign function world_enable_sleeping(world_id: WorldId, flag: bool) -> void = c.b2World_EnableSleeping
239
239
  public foreign function world_is_sleeping_enabled(world_id: WorldId) -> bool = c.b2World_IsSleepingEnabled
240
240
  public foreign function world_enable_continuous(world_id: WorldId, flag: bool) -> void = c.b2World_EnableContinuous
@@ -243,8 +243,8 @@ public foreign function world_set_restitution_threshold(world_id: WorldId, value
243
243
  public foreign function world_get_restitution_threshold(world_id: WorldId) -> float = c.b2World_GetRestitutionThreshold
244
244
  public foreign function world_set_hit_event_threshold(world_id: WorldId, value: float) -> void = c.b2World_SetHitEventThreshold
245
245
  public foreign function world_get_hit_event_threshold(world_id: WorldId) -> float = c.b2World_GetHitEventThreshold
246
- public foreign function world_set_custom_filter_callback(world_id: WorldId, fcn: ptr[CustomFilterFcn], context: ptr[void]) -> void = c.b2World_SetCustomFilterCallback
247
- public foreign function world_set_pre_solve_callback(world_id: WorldId, fcn: ptr[PreSolveFcn], context: ptr[void]) -> void = c.b2World_SetPreSolveCallback
246
+ public foreign function world_set_custom_filter_callback(world_id: WorldId, fcn: CustomFilterFcn, context: ptr[void]) -> void = c.b2World_SetCustomFilterCallback
247
+ public foreign function world_set_pre_solve_callback(world_id: WorldId, fcn: PreSolveFcn, context: ptr[void]) -> void = c.b2World_SetPreSolveCallback
248
248
  public foreign function world_set_gravity(world_id: WorldId, gravity: Vec2) -> void = c.b2World_SetGravity
249
249
  public foreign function world_get_gravity(world_id: WorldId) -> Vec2 = c.b2World_GetGravity
250
250
  public foreign function world_explode(world_id: WorldId, in explosion_def: ExplosionDef) -> void = c.b2World_Explode
@@ -258,8 +258,8 @@ public foreign function world_get_profile(world_id: WorldId) -> Profile = c.b2Wo
258
258
  public foreign function world_get_counters(world_id: WorldId) -> Counters = c.b2World_GetCounters
259
259
  public foreign function world_set_user_data(world_id: WorldId, user_data: ptr[void]) -> void = c.b2World_SetUserData
260
260
  public foreign function world_get_user_data(world_id: WorldId) -> ptr[void] = c.b2World_GetUserData
261
- public foreign function world_set_friction_callback(world_id: WorldId, callback: ptr[FrictionCallback]) -> void = c.b2World_SetFrictionCallback
262
- public foreign function world_set_restitution_callback(world_id: WorldId, callback: ptr[RestitutionCallback]) -> void = c.b2World_SetRestitutionCallback
261
+ public foreign function world_set_friction_callback(world_id: WorldId, callback: FrictionCallback) -> void = c.b2World_SetFrictionCallback
262
+ public foreign function world_set_restitution_callback(world_id: WorldId, callback: RestitutionCallback) -> void = c.b2World_SetRestitutionCallback
263
263
  public foreign function world_dump_memory_stats(world_id: WorldId) -> void = c.b2World_DumpMemoryStats
264
264
  public foreign function world_rebuild_static_tree(world_id: WorldId) -> void = c.b2World_RebuildStaticTree
265
265
  public foreign function world_enable_speculative(world_id: WorldId, flag: bool) -> void = c.b2World_EnableSpeculative
data/std/c/box2d.mt CHANGED
@@ -10,9 +10,9 @@ type b2AllocFcn = fn(arg0: uint, arg1: int) -> ptr[void]
10
10
  type b2FreeFcn = fn(arg0: ptr[void]) -> void
11
11
  type b2AssertFcn = fn(arg0: cstr, arg1: cstr, arg2: int) -> int
12
12
 
13
- external function b2SetAllocator(allocFcn: ptr[b2AllocFcn], freeFcn: ptr[b2FreeFcn]) -> void
13
+ external function b2SetAllocator(allocFcn: b2AllocFcn, freeFcn: b2FreeFcn) -> void
14
14
  external function b2GetByteCount() -> int
15
- external function b2SetAssertFcn(assertFcn: ptr[b2AssertFcn]) -> void
15
+ external function b2SetAssertFcn(assertFcn: b2AssertFcn) -> void
16
16
 
17
17
  struct b2Version:
18
18
  major: int
@@ -313,15 +313,15 @@ external function b2DynamicTree_GetCategoryBits(tree: ptr[b2DynamicTree], proxyI
313
313
 
314
314
  type b2TreeQueryCallbackFcn = fn(arg0: int, arg1: ulong, arg2: ptr[void]) -> bool
315
315
 
316
- external function b2DynamicTree_Query(tree: const_ptr[b2DynamicTree], aabb: b2AABB, maskBits: ptr_uint, callback: ptr[b2TreeQueryCallbackFcn], context: ptr[void]) -> b2TreeStats
316
+ external function b2DynamicTree_Query(tree: const_ptr[b2DynamicTree], aabb: b2AABB, maskBits: ptr_uint, callback: b2TreeQueryCallbackFcn, context: ptr[void]) -> b2TreeStats
317
317
 
318
318
  type b2TreeRayCastCallbackFcn = fn(arg0: const_ptr[b2RayCastInput], arg1: int, arg2: ulong, arg3: ptr[void]) -> float
319
319
 
320
- external function b2DynamicTree_RayCast(tree: const_ptr[b2DynamicTree], input: const_ptr[b2RayCastInput], maskBits: ptr_uint, callback: ptr[b2TreeRayCastCallbackFcn], context: ptr[void]) -> b2TreeStats
320
+ external function b2DynamicTree_RayCast(tree: const_ptr[b2DynamicTree], input: const_ptr[b2RayCastInput], maskBits: ptr_uint, callback: b2TreeRayCastCallbackFcn, context: ptr[void]) -> b2TreeStats
321
321
 
322
322
  type b2TreeShapeCastCallbackFcn = fn(arg0: const_ptr[b2ShapeCastInput], arg1: int, arg2: ulong, arg3: ptr[void]) -> float
323
323
 
324
- external function b2DynamicTree_ShapeCast(tree: const_ptr[b2DynamicTree], input: const_ptr[b2ShapeCastInput], maskBits: ptr_uint, callback: ptr[b2TreeShapeCastCallbackFcn], context: ptr[void]) -> b2TreeStats
324
+ external function b2DynamicTree_ShapeCast(tree: const_ptr[b2DynamicTree], input: const_ptr[b2ShapeCastInput], maskBits: ptr_uint, callback: b2TreeShapeCastCallbackFcn, context: ptr[void]) -> b2TreeStats
325
325
  external function b2DynamicTree_GetHeight(tree: const_ptr[b2DynamicTree]) -> int
326
326
  external function b2DynamicTree_GetAreaRatio(tree: const_ptr[b2DynamicTree]) -> float
327
327
  external function b2DynamicTree_GetRootBounds(tree: const_ptr[b2DynamicTree]) -> b2AABB
@@ -382,7 +382,7 @@ const b2_nullChainId: b2ChainId = b2ChainId(index1 = 0, world0 = 0, generation =
382
382
  const b2_nullJointId: b2JointId = b2JointId(index1 = 0, world0 = 0, generation = 0)
383
383
 
384
384
  type b2TaskCallback = fn(arg0: int, arg1: int, arg2: uint, arg3: ptr[void]) -> void
385
- type b2EnqueueTaskCallback = fn(arg0: ptr[b2TaskCallback], arg1: int, arg2: int, arg3: ptr[void], arg4: ptr[void]) -> ptr[void]
385
+ type b2EnqueueTaskCallback = fn(arg0: b2TaskCallback, arg1: int, arg2: int, arg3: ptr[void], arg4: ptr[void]) -> ptr[void]
386
386
  type b2FinishTaskCallback = fn(arg0: ptr[void], arg1: ptr[void]) -> void
387
387
  type b2FrictionCallback = fn(arg0: float, arg1: int, arg2: float, arg3: int) -> float
388
388
  type b2RestitutionCallback = fn(arg0: float, arg1: int, arg2: float, arg3: int) -> float
@@ -404,13 +404,13 @@ struct b2WorldDef:
404
404
  contactDampingRatio: float
405
405
  maxContactPushSpeed: float
406
406
  maximumLinearSpeed: float
407
- frictionCallback: ptr[b2FrictionCallback]
408
- restitutionCallback: ptr[b2RestitutionCallback]
407
+ frictionCallback: b2FrictionCallback
408
+ restitutionCallback: b2RestitutionCallback
409
409
  enableSleep: bool
410
410
  enableContinuous: bool
411
411
  workerCount: int
412
- enqueueTask: ptr[b2EnqueueTaskCallback]
413
- finishTask: ptr[b2FinishTaskCallback]
412
+ enqueueTask: b2EnqueueTaskCallback
413
+ finishTask: b2FinishTaskCallback
414
414
  userTaskContext: ptr[void]
415
415
  userData: ptr[void]
416
416
  internalValue: int
@@ -942,13 +942,13 @@ external function b2World_Draw(worldId: b2WorldId, draw: ptr[b2DebugDraw]) -> vo
942
942
  external function b2World_GetBodyEvents(worldId: b2WorldId) -> b2BodyEvents
943
943
  external function b2World_GetSensorEvents(worldId: b2WorldId) -> b2SensorEvents
944
944
  external function b2World_GetContactEvents(worldId: b2WorldId) -> b2ContactEvents
945
- external function b2World_OverlapAABB(worldId: b2WorldId, aabb: b2AABB, filter: b2QueryFilter, fcn: ptr[b2OverlapResultFcn], context: ptr[void]) -> b2TreeStats
946
- external function b2World_OverlapShape(worldId: b2WorldId, proxy: const_ptr[b2ShapeProxy], filter: b2QueryFilter, fcn: ptr[b2OverlapResultFcn], context: ptr[void]) -> b2TreeStats
947
- external function b2World_CastRay(worldId: b2WorldId, origin: b2Vec2, translation: b2Vec2, filter: b2QueryFilter, fcn: ptr[b2CastResultFcn], context: ptr[void]) -> b2TreeStats
945
+ external function b2World_OverlapAABB(worldId: b2WorldId, aabb: b2AABB, filter: b2QueryFilter, fcn: b2OverlapResultFcn, context: ptr[void]) -> b2TreeStats
946
+ external function b2World_OverlapShape(worldId: b2WorldId, proxy: const_ptr[b2ShapeProxy], filter: b2QueryFilter, fcn: b2OverlapResultFcn, context: ptr[void]) -> b2TreeStats
947
+ external function b2World_CastRay(worldId: b2WorldId, origin: b2Vec2, translation: b2Vec2, filter: b2QueryFilter, fcn: b2CastResultFcn, context: ptr[void]) -> b2TreeStats
948
948
  external function b2World_CastRayClosest(worldId: b2WorldId, origin: b2Vec2, translation: b2Vec2, filter: b2QueryFilter) -> b2RayResult
949
- external function b2World_CastShape(worldId: b2WorldId, proxy: const_ptr[b2ShapeProxy], translation: b2Vec2, filter: b2QueryFilter, fcn: ptr[b2CastResultFcn], context: ptr[void]) -> b2TreeStats
949
+ external function b2World_CastShape(worldId: b2WorldId, proxy: const_ptr[b2ShapeProxy], translation: b2Vec2, filter: b2QueryFilter, fcn: b2CastResultFcn, context: ptr[void]) -> b2TreeStats
950
950
  external function b2World_CastMover(worldId: b2WorldId, mover: const_ptr[b2Capsule], translation: b2Vec2, filter: b2QueryFilter) -> float
951
- external function b2World_CollideMover(worldId: b2WorldId, mover: const_ptr[b2Capsule], filter: b2QueryFilter, fcn: ptr[b2PlaneResultFcn], context: ptr[void]) -> void
951
+ external function b2World_CollideMover(worldId: b2WorldId, mover: const_ptr[b2Capsule], filter: b2QueryFilter, fcn: b2PlaneResultFcn, context: ptr[void]) -> void
952
952
  external function b2World_EnableSleeping(worldId: b2WorldId, flag: bool) -> void
953
953
  external function b2World_IsSleepingEnabled(worldId: b2WorldId) -> bool
954
954
  external function b2World_EnableContinuous(worldId: b2WorldId, flag: bool) -> void
@@ -957,8 +957,8 @@ external function b2World_SetRestitutionThreshold(worldId: b2WorldId, value: flo
957
957
  external function b2World_GetRestitutionThreshold(worldId: b2WorldId) -> float
958
958
  external function b2World_SetHitEventThreshold(worldId: b2WorldId, value: float) -> void
959
959
  external function b2World_GetHitEventThreshold(worldId: b2WorldId) -> float
960
- external function b2World_SetCustomFilterCallback(worldId: b2WorldId, fcn: ptr[b2CustomFilterFcn], context: ptr[void]) -> void
961
- external function b2World_SetPreSolveCallback(worldId: b2WorldId, fcn: ptr[b2PreSolveFcn], context: ptr[void]) -> void
960
+ external function b2World_SetCustomFilterCallback(worldId: b2WorldId, fcn: b2CustomFilterFcn, context: ptr[void]) -> void
961
+ external function b2World_SetPreSolveCallback(worldId: b2WorldId, fcn: b2PreSolveFcn, context: ptr[void]) -> void
962
962
  external function b2World_SetGravity(worldId: b2WorldId, gravity: b2Vec2) -> void
963
963
  external function b2World_GetGravity(worldId: b2WorldId) -> b2Vec2
964
964
  external function b2World_Explode(worldId: b2WorldId, explosionDef: const_ptr[b2ExplosionDef]) -> void
@@ -972,8 +972,8 @@ external function b2World_GetProfile(worldId: b2WorldId) -> b2Profile
972
972
  external function b2World_GetCounters(worldId: b2WorldId) -> b2Counters
973
973
  external function b2World_SetUserData(worldId: b2WorldId, userData: ptr[void]) -> void
974
974
  external function b2World_GetUserData(worldId: b2WorldId) -> ptr[void]
975
- external function b2World_SetFrictionCallback(worldId: b2WorldId, callback: ptr[b2FrictionCallback]) -> void
976
- external function b2World_SetRestitutionCallback(worldId: b2WorldId, callback: ptr[b2RestitutionCallback]) -> void
975
+ external function b2World_SetFrictionCallback(worldId: b2WorldId, callback: b2FrictionCallback) -> void
976
+ external function b2World_SetRestitutionCallback(worldId: b2WorldId, callback: b2RestitutionCallback) -> void
977
977
  external function b2World_DumpMemoryStats(worldId: b2WorldId) -> void
978
978
  external function b2World_RebuildStaticTree(worldId: b2WorldId) -> void
979
979
  external function b2World_EnableSpeculative(worldId: b2WorldId, flag: bool) -> void
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mt-lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.34
4
+ version: 0.3.37
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -624,7 +624,7 @@ metadata:
624
624
  homepage_uri: https://teefan.github.io/mt-lang/
625
625
  source_code_uri: https://github.com/teefan/mt-lang
626
626
  post_install_message: |
627
- Milk Tea 0.3.34 installed!
627
+ Milk Tea 0.3.37 installed!
628
628
 
629
629
  System requirements:
630
630
  - A C compiler (gcc or clang) must be available on PATH