mt-lang 0.3.28 → 0.3.31

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: 20058db447f681f83bcc651d9a9772e79b23bd9f1677e5c33d2053bab536a14f
4
- data.tar.gz: 204af7f1018ce7637690eddc2bc9a7f297fc1f10e59c1a573491bd3b7c0b0d1c
3
+ metadata.gz: 16833c6b95c7e580f12199a381802f47ac628b7252f4bc1647714098cbd4f824
4
+ data.tar.gz: 296b0547096ceee9d394edc13df6f042e2b8ed6d42c98b4745e1b39885407b85
5
5
  SHA512:
6
- metadata.gz: ec63256de42954560cedc8e5021370b8f45a64e083ebb839d3057d4ea93816a51d45f950283ece162152ad10be6f1bb6ec4aa54a6ad8ce7b2db107830dd1db6a
7
- data.tar.gz: 616bc238b0ee79df60a7a09fc62b895b1ab7717a1116a6b96c50879549578ba0aab91fbb9a74e40aedfc559973c333a8bc2c2cc40d3d16b6913584a744230819
6
+ metadata.gz: '080aa9b4ad981dce767373642fe8c81fef77bf4b4c364b60e2b5dc633e7e1a2f593187ebcc3903a15aebec587bd6989a844dfd8fd842fb8b61fc855a5ba9ce86'
7
+ data.tar.gz: b9c60e1298c8fb8195401875f3d77358965741c1537cb674696d71ca1b5ef8f8ce5289d7a4e3491bbca24bd485481d6fd65df531a975237c08da888539c30b9e
data/README.md CHANGED
@@ -94,7 +94,7 @@ Supported literals:
94
94
  - floats: `3.14`, `1.2e-3`, `1.0f` (float suffix), `1.0d` (double suffix)
95
95
  - character: `'a'`, `'\n'`, `'\t'`, `'\\'`, `'\''`, `'\0'`, `'\x41'`. Type is `ubyte`. Escape sequences: `\n`, `\r`, `\t`, `\\`, `\'`, `\"`, `\0` (null byte), `\xNN` (hex byte).
96
96
  - booleans: `true`, `false`
97
- - string: `"hello"` -> `str`
97
+ - string: `"hello"` -> `str`. The `+` operator concatenates `str` values (see §12).
98
98
  - cstring: `c"hello"` -> `cstr`
99
99
  - heredoc string: `<<-TAG ... TAG`
100
100
  - heredoc cstring: `c<<-TAG ... TAG`
@@ -332,6 +332,8 @@ Rules:
332
332
 
333
333
  Enum and flags values support the full set of comparison operators (`==`, `!=`, `<`, `<=`, `>`, `>=`) against values of the same enum type and against their backing integer type. Comparisons use the underlying integer backing values. Flags also support bitwise operators (`|`, `&`, `^`, `~`).
334
334
 
335
+ Struct and variant values support `==` and `!=`: structs compare field by field when all fields are comparable, variants compare the active arm and its payload. Recursive variants compare cyclic fields by pointer identity. Untagged `union` fields and non-comparable types (`proc`, `span`, `simd`, `SoA`, `Task`, `dyn`) are rejected — use `equal[T]` there.
336
+
335
337
  Generic variants and structs are supported, for example `Option[int]`.
336
338
 
337
339
  ## 6. Interfaces And Methods
@@ -808,7 +810,7 @@ Type constructors:
808
810
 
809
811
  - `ptr[T]`
810
812
  - `const_ptr[T]`
811
- - `own[T]` — owning heap pointer: auto-dereferences like `ref` but storable, returnable, and nullable. Created via `heap.must_alloc[T](count)`. Compiles to `T*`.
813
+ - `own[T]` — owning heap pointer with auto-deref; storable, returnable, nullable (compiles to `T*`)
812
814
  - `ref[T]`
813
815
  - `span[T]`
814
816
  - `array[T, N]`
@@ -819,9 +821,9 @@ Type constructors:
819
821
  - `fn(params...) -> R`
820
822
  - `proc(params...) -> R`
821
823
  - `SoA[T, N]` — Structure-of-Arrays: each struct field becomes a separate array of length `N`; access `soa[i].field` reads from column `field` at row `i`
822
- - `simd[T, N]` — SIMD vector: `N` numeric lanes (128 or 256 bits). Component-wise `+` `-` `*` `/` `%` `&` `|` `^` `~` `<<` `>>`. Lane access via `[i]` (compile-time index). Lowers to GCC/Clang vector extensions for portable x86/ARM vector code.
824
+ - `simd[T, N]` — SIMD vector of `N` numeric lanes (128 or 256 bits); component-wise `+` `-` `*` `/` `%` `&` `|` `^` `~` `<<` `>>`; lane access via `[i]`. Lowers to GCC/Clang vector extensions.
823
825
  - `dyn[InterfaceName]` — runtime interface value (fat pointer: `{ void* data, void* vtable }`). Constructed via `adapt[Interface](value: ref[T])`. @see §6.
824
- - `atomic[T]` — atomic value for lock-free concurrent access. `T` must be a primitive integer or `bool`. Methods: `load() -> T`, `store(value: T)`, `add(value: T) -> T`, `sub(value: T) -> T`, `exchange(value: T) -> T`. All operations use sequential consistency. Lowers to C11 `_Atomic T` with `__atomic_*` builtins.
826
+ - `atomic[T]` — lock-free atomic value (`T` = primitive integer or `bool`) with `load`/`store`/`add`/`sub`/`exchange`; sequential consistency. Lowers to C11 `_Atomic T`.
825
827
  - `(T, U)` — tuple type. Positional fields auto-named `_0`, `_1`. Named fields use `(x = T, y = U)`. Copy by value, returns supported.
826
828
 
827
829
  When a `span[T]` is expected, an addressable `array[T, N]` value may be passed directly via implicit boundary coercion. For explicit conversion, `array.as_span()` returns `span[T]` without requiring a boundary context.
@@ -937,7 +939,7 @@ See module source for full method surface. Iterator forms:
937
939
 
938
940
  Text categories:
939
941
 
940
- - `str` -> string view
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.
941
943
  - `cstr` -> C ABI string
942
944
  - `str_buffer[N]` -> fixed-capacity mutable UTF-8 text buffer
943
945
 
@@ -1141,8 +1143,8 @@ Current compiler rejects:
1141
1143
 
1142
1144
  ### Operator and expression restrictions
1143
1145
 
1144
- - `+` does not support `str`/`cstr` concatenation; use format strings or `std.str` helpers
1145
- - `==` and `!=` are not supported on struct types; use `equal[T]`. Variant types support `==`/`!=` (generates per-variant comparison helper).
1146
+ - `+` concatenates `str`; `cstr` and mixed `str`/`cstr` concatenation are not supported
1147
+ - `==`/`!=` on structs and variants requires all fields equality-comparable; use `equal[T]` otherwise
1146
1148
  - range expressions are restricted to `for`-loop iterables and range-index assignment targets
1147
1149
  - functions, methods, generic functions, and variant arms must be called — they are not usable as bare values
1148
1150
  - `read(...)` of a raw pointer requires `unsafe`
data/docs/index.html CHANGED
@@ -1149,6 +1149,25 @@ variant Result[T, E]:
1149
1149
  <strong>Prelude types.</strong> <code>Option[T]</code> and <code>Result[T, E]</code> are auto-imported — no <code>import</code> statement is needed. They are available in every Milk Tea source file. Their extending methods (<code>is_some</code>, <code>unwrap</code>, <code>is_success</code>, <code>map_error</code>, etc.) are always accessible.
1150
1150
  </div>
1151
1151
 
1152
+ <h3>Struct &amp; Variant Equality</h3>
1153
+ <p>Structs and variants support <code>==</code> and <code>!=</code> with field-wise comparison. A struct compares equal when every field compares equal; a variant compares equal when the same arm is active and its payload fields are equal. Every field must be equality-comparable (nested structs, arrays, nullable values, <code>str</code>, enums, flags, pointers, ...). Recursive variants embed cyclic fields as pointers and compare those by pointer identity. Untagged <code>union</code> fields and non-comparable types (<code>proc</code>, <code>span</code>, <code>simd</code>, <code>SoA</code>, <code>Task</code>, <code>dyn</code>) are rejected; use <code>equal[T](...)</code> there.</p>
1154
+ <div class="code-wrap">
1155
+ <button class="copy-btn" onclick="copyCode(this)">Copy</button>
1156
+ <pre><code>struct Vec2:
1157
+ x: float
1158
+ y: float
1159
+
1160
+ variant Shape:
1161
+ circle(radius: float)
1162
+ square(side: float)
1163
+
1164
+ function same_point(a: Vec2, b: Vec2) -&gt; bool:
1165
+ return a == b
1166
+
1167
+ function same_shape(a: Shape, b: Shape) -&gt; bool:
1168
+ return a == b</code></pre>
1169
+ </div>
1170
+
1152
1171
  <h3>Opaque</h3>
1153
1172
  <p>For C handles whose layout is unknown. Opaque types may implement interfaces, enabling constrained generics and <code>dyn</code> dispatch over C handles.</p>
1154
1173
  <div class="code-wrap">
@@ -1243,7 +1262,7 @@ let d: dyn[Drawable] = adapt[Drawable](ref_of(entity))</code></pre>
1243
1262
  <tr><td><code>ptr_int</code> <code>ptr_uint</code></td><td>Pointer-sized integers</td></tr>
1244
1263
  <tr><td><code>float</code> <code>double</code></td><td>Floating-point</td></tr>
1245
1264
  <tr><td><code>void</code></td><td>No value</td></tr>
1246
- <tr><td><code>str</code></td><td>UTF-8 string view (borrowed)</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>
1247
1266
  <tr><td><code>cstr</code></td><td>NUL-terminated C string</td></tr>
1248
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>
1249
1268
  <tr><td><code>ivec2</code> <code>ivec3</code> <code>ivec4</code></td><td>Integer vectors</td></tr>
@@ -1966,8 +1985,8 @@ MSG</code></pre>
1966
1985
  <tr><td><code>%</code> requires integer operands</td><td></td></tr>
1967
1986
  <tr><td>Bitwise ops require matching types</td><td>Integer or flags types</td></tr>
1968
1987
  <tr><td>Shift ops require integer operands</td><td></td></tr>
1969
- <tr><td>No implicit struct equality</td><td>Structs cannot be compared with <code>==</code>/<code>!=</code>. Use <code>equal[T](a, b)</code>. Variants <em>do</em> support <code>==</code> and <code>!=</code> with generated per-type comparison.</td></tr>
1970
- <tr><td>No <code>str</code>/<code>cstr</code> concatenation</td><td>Use format strings or <code>std.str</code> helpers</td></tr>
1988
+ <tr><td>Aggregate equality requires comparable fields</td><td>Structs and variants support <code>==</code>/<code>!=</code> when every field is equality-comparable. Untagged <code>union</code> fields and non-comparable types (<code>proc</code>, <code>span</code>, <code>simd</code>, <code>SoA</code>, <code>Task</code>, <code>dyn</code>) are rejected — use <code>equal[T](a, b)</code> there. Recursive variants compare cyclic fields by pointer identity.</td></tr>
1989
+ <tr><td>No <code>cstr</code> concatenation</td><td><code>+</code> concatenates <code>str</code>; <code>cstr</code> and mixed <code>str</code>/<code>cstr</code> are rejected</td></tr>
1971
1990
  <tr><td>Compile-time constant fit</td><td>Exact compile-time numeric constants (literals, <code>const</code> values) fit an explicit numeric target without a manual cast when representable exactly</td></tr>
1972
1991
  <tr><td>Integer-to-float at typed boundaries</td><td>A primitive integer expression may flow into an expected float type for explicit typed locals, assignments, returns, function arguments, or field initializers. Integer arithmetic stays integer arithmetic until that final boundary cast.</td></tr>
1973
1992
  </table>
@@ -2408,8 +2427,8 @@ import std.math # intentionally available for downstream</code></pre>
2408
2427
  <li>Conditions must be <code>bool</code>; no truthy/falsy coercion</li>
2409
2428
  <li>Mixed signed/unsigned arithmetic requires explicit cast; non-widening integer conversions (narrowing, signed → unsigned) need explicit <code>T&lt;-value</code></li>
2410
2429
  <li>Enum and flags values do not implicitly coerce to backing integers</li>
2411
- <li><code>+</code> does not support <code>str</code>/<code>cstr</code> concatenation</li>
2412
- <li><code>==</code> and <code>!=</code> not supported on struct types; use <code>equal[T]</code></li>
2430
+ <li><code>+</code> concatenates <code>str</code>; <code>cstr</code> and mixed <code>str</code>/<code>cstr</code> are not concatenable</li>
2431
+ <li><code>==</code>/<code>!=</code> on structs and variants requires all fields equality-comparable; use <code>equal[T]</code> for non-comparable types</li>
2413
2432
  <li>A statement cannot begin with a binary operator (including <code>+</code>, <code>-</code>, <code>and</code>, <code>or</code>, <code>is</code>, <code>..</code>); continuation requires ending the previous line with the operator or wrapping in <code>()</code></li>
2414
2433
  <li>Bare function and method names are not usable as values (cannot be assigned or passed without calling). Use <code>fn(...)</code> function pointer types or <code>proc(...)</code> closures for callable values.</li>
2415
2434
  </ul>
@@ -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. Any 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 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.
42
42
 
43
43
  ### 2. C is the ABI ground truth
44
44
 
@@ -762,6 +762,8 @@ 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.
766
+
765
767
  Built-in vector, matrix, and quaternion types support component-wise arithmetic with the standard operators:
766
768
 
767
769
  - Vectors (`vecN`/`ivecN`): `+`, `-`, `*` (component-wise same-type); `*`, `/` (scalar); unary `-`
@@ -124,7 +124,7 @@ Supported literals:
124
124
  - integer: `42`, `0xff`, `0b1010`, with `_` separators. Integer type suffixes: `42u` (`uint`), `0xFFub` (`ubyte`), `100z` (`ptr_uint`), `7i` (`int`), `-1l` (`long`), etc.
125
125
  - float: `3.14`, `1.2e-3`, `1.1920929E-7`, `1.0f` (float suffix), `1.0d` (double suffix)
126
126
  - character: `'a'`, `'\n'`, `'\t'`, `'\\'`, `'\''`, `'\0'`, `'\x41'`. Type is `ubyte`. Escape sequences: `\n`, `\r`, `\t`, `\\`, `\'`, `\"`, `\0` (null), `\xNN` (hex byte).
127
- - string: `"hello"` (`str`). Supported string escapes are `\n`, `\r`, `\t`, `\0` (null), `\"`, `\'`, and `\\`; any other `\x` sequence is taken literally. Hex byte escapes (`\xNN`) are character-literal only, not string-literal.
127
+ - string: `"hello"` (`str`). String escapes: `\n`, `\r`, `\t`, `\0`, `\"`, `\'`, `\\`; any other `\x` sequence is taken literally, and hex byte escapes (`\xNN`) are character-literal only. `str + str` concatenates (§5.3); see §12 for text-building details.
128
128
  - cstring: `c"hello"` (`cstr`)
129
129
  - heredoc string: `<<-TAG ... TAG` (`str`)
130
130
  - heredoc cstring: `c<<-TAG ... TAG` (`cstr`)
@@ -412,6 +412,60 @@ const IS_AFTER: bool = State.running > State.idle
412
412
 
413
413
  Flags values also support bitwise operators (`|`, `&`, `^`, `~`) where both operands share the same flags type.
414
414
 
415
+ ### 3.4b Struct and variant equality
416
+
417
+ Struct and variant values support `==` and `!=`. The compiler generates a
418
+ field-wise comparison helper per compared aggregate type.
419
+
420
+ Rules for structs:
421
+
422
+ - Two struct values compare equal when every field compares equal, in field
423
+ order. Comparison is field-by-field, never a raw byte compare.
424
+ - A struct is equality-comparable when every field is itself
425
+ equality-comparable: primitives, enums, flags, `str`, pointers, `ref[T]`,
426
+ `fn(...)`, opaque, other comparable structs, comparable variants, arrays of
427
+ comparable elements, and value nullables of comparable bases.
428
+ - Untagged `union` fields and non-comparable field types (`proc`, `span`,
429
+ `simd`, `SoA`, `Task`, `dyn`) are rejected with a field-level error. Use
430
+ `equal[T](...)` for those.
431
+
432
+ Rules for variants:
433
+
434
+ - Two variant values compare equal when the same arm is active and every
435
+ payload field of that arm compares equal. No-payload arms compare by
436
+ discriminant only.
437
+ - Recursive (cyclic) variants are supported: the C backend embeds cyclic
438
+ fields as pointers, and those fields compare by pointer identity. A
439
+ separately-constructed recursive value therefore compares unequal to
440
+ another value of identical shape.
441
+ - Variant arm payload bindings (`match v: Token.ident as p:`) compare as
442
+ payload structs.
443
+
444
+ General rules:
445
+
446
+ - `==`/`!=` on struct and variant constants folds at compile time.
447
+ - Comparison operands must have the same aggregate type.
448
+ - The canonical `equal[T](...)` hook remains available for types without
449
+ `==` support and for custom equality semantics.
450
+
451
+ Example:
452
+
453
+ ```mt
454
+ struct Vec2:
455
+ x: float
456
+ y: float
457
+
458
+ variant Shape:
459
+ circle(radius: float)
460
+ square(side: float)
461
+
462
+ function same_point(a: Vec2, b: Vec2) -> bool:
463
+ return a == b
464
+
465
+ function same_shape(a: Shape, b: Shape) -> bool:
466
+ return a == b
467
+ ```
468
+
415
469
  ### 3.5 Interfaces
416
470
 
417
471
  ```mt
@@ -1018,6 +1072,8 @@ Rules:
1018
1072
  11. `+`, `-`
1019
1073
  12. `*`, `/`, `%`
1020
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.
1076
+
1021
1077
  ### 5.4 Assignment operators
1022
1078
 
1023
1079
  - `=`
@@ -1322,7 +1378,7 @@ Custom formatting hook notes:
1322
1378
  - `read(...)` of raw pointer requires `unsafe`
1323
1379
  - pointer casts require `unsafe`
1324
1380
  - `reinterpret[...]` requires `unsafe`, non-array concrete sized types, and equal-size source and target types.
1325
- - variant types support `==` and `!=` (generates per-variant comparison helper)
1381
+ - struct and variant types support `==` and `!=` (generates a per-type comparison helper; see §3.4b)
1326
1382
 
1327
1383
  ## 10. Async Semantics
1328
1384
 
@@ -1564,8 +1620,8 @@ The compiler intentionally rejects the following patterns. These are design cons
1564
1620
 
1565
1621
  ### 13.7 Operator and expression restrictions
1566
1622
 
1567
- - `+` does not support `str`/`cstr` concatenation
1568
- - `==` and `!=` are not supported on struct types; use `equal[T]`
1623
+ - `+` concatenates `str`; `cstr` and mixed `str`/`cstr` are not concatenable
1624
+ - `==`/`!=` on structs and variants requires all fields equality-comparable; use `equal[T]` otherwise (§3.4b)
1569
1625
  - range expressions are restricted to `for`-loop iterables and range-index assignment targets
1570
1626
  - functions, methods, generic functions, and variant arms must be called — they are not usable as bare values
1571
1627
  - `read(...)` of a raw pointer requires `unsafe`
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.28"
6
+ VERSION = "0.3.31"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -47,6 +47,7 @@ module MilkTea
47
47
  when IR::Binary
48
48
  return emit_string_equality_expression(expression) if string_equality_expression?(expression)
49
49
  return emit_variant_equality_expression(expression) if variant_equality_expression?(expression)
50
+ return emit_struct_equality_expression(expression) if struct_equality_expression?(expression)
50
51
  return emit_nullable_null_comparison(expression) if nullable_null_comparison?(expression)
51
52
 
52
53
  emit_binary_expression(expression)
@@ -181,8 +182,27 @@ module MilkTea
181
182
  end
182
183
 
183
184
  def variant_equality_helper_name(type)
184
- variant = type.is_a?(Types::VariantArmPayload) ? type.variant_type : type
185
- "mt_variant_eq_#{named_type_c_name(variant)}"
185
+ return "mt_struct_eq_#{named_type_c_name(type)}" if type.is_a?(Types::VariantArmPayload)
186
+
187
+ "mt_variant_eq_#{named_type_c_name(type)}"
188
+ end
189
+
190
+ def struct_equality_expression?(expression)
191
+ EQUALITY_OPERATORS.include?(expression.operator) && struct_equality_type?(expression.left.type)
192
+ end
193
+
194
+ def struct_equality_type?(type)
195
+ type.is_a?(Types::Struct) && !type.is_a?(Types::Union) && !type.is_a?(Types::VariantArmPayload)
196
+ end
197
+
198
+ def emit_struct_equality_expression(expression)
199
+ helper_name = struct_equality_helper_name(expression.left.type)
200
+ call = "#{helper_name}(#{emit_expression(expression.left)}, #{emit_expression(expression.right)})"
201
+ expression.operator == "!=" ? "!#{call}" : call
202
+ end
203
+
204
+ def struct_equality_helper_name(type)
205
+ "mt_struct_eq_#{named_type_c_name(type)}"
186
206
  end
187
207
 
188
208
  def nullable_value_type?(type)
@@ -289,42 +289,140 @@ module MilkTea
289
289
  emitted_functions.any? { |function| function_uses_str_equality?(function) }
290
290
  end
291
291
 
292
+ def uses_str_concat_helper?
293
+ return @uses_str_concat if defined?(@uses_str_concat)
294
+
295
+ @uses_str_concat = emitted_functions.any? do |function|
296
+ function_uses_named_call?(function, %w[mt_str_concat])
297
+ end
298
+ end
299
+
292
300
  def uses_variant_equality_helper?
293
- emitted_functions.any? { |function| function_uses_variant_equality?(function) }
301
+ !variant_equality_types.empty?
294
302
  end
295
303
 
296
- def function_uses_variant_equality?(function)
297
- function.body.any? { |statement| statement_uses_variant_equality?(statement) }
304
+ def uses_struct_equality_helper?
305
+ !struct_equality_types.empty?
298
306
  end
299
307
 
300
- def statement_uses_variant_equality?(statement)
301
- case statement
302
- when IR::LocalDecl
303
- expression_uses_variant_equality?(statement.value)
304
- when IR::ExpressionStmt
305
- expression_uses_variant_equality?(statement.expression)
306
- when IR::ReturnStmt
307
- statement.value && expression_uses_variant_equality?(statement.value)
308
- when IR::Assignment
309
- expression_uses_variant_equality?(statement.value)
310
- when IR::IfStmt
311
- expression_uses_variant_equality?(statement.condition)
312
- when IR::WhileStmt
313
- expression_uses_variant_equality?(statement.condition)
314
- else
315
- false
308
+ # Aggregate types (structs and variants) compared with ==/!= anywhere in
309
+ # the program, transitively closed over value-typed fields so nested
310
+ # aggregates used inside a comparison also get an equality helper.
311
+ def aggregate_equality_types
312
+ @aggregate_equality_types ||= begin
313
+ types = Set.new
314
+ emitted_functions.each do |function|
315
+ function.body.each do |statement|
316
+ statement_matches_expression_predicate?(
317
+ statement,
318
+ expression_pred: ->(expression) { collect_aggregate_equality_from_expression(expression, types) },
319
+ )
320
+ end
321
+ end
322
+ types
316
323
  end
317
324
  end
318
325
 
319
- def expression_uses_variant_equality?(expression)
320
- return false unless expression
326
+ def struct_equality_types
327
+ @struct_equality_types ||= aggregate_equality_types.select { |type| struct_equality_type?(type) || type.is_a?(Types::VariantArmPayload) }
328
+ end
321
329
 
330
+ def variant_equality_types
331
+ @variant_equality_types ||= aggregate_equality_types.select { |type| type.is_a?(Types::Variant) }
332
+ end
333
+
334
+ def aggregate_equality_type?(type)
335
+ struct_equality_type?(type) || type.is_a?(Types::Variant) || type.is_a?(Types::VariantArmPayload)
336
+ end
337
+
338
+ def collect_aggregate_equality_from_expression(expression, types)
322
339
  case expression
323
340
  when IR::Binary
324
- EQUALITY_OPERATORS.include?(expression.operator) &&
325
- (expression.left.type.is_a?(Types::Variant) || expression.left.type.is_a?(Types::VariantArmPayload))
341
+ collect_aggregate_equality_from_expression(expression.left, types)
342
+ collect_aggregate_equality_from_expression(expression.right, types)
343
+ if EQUALITY_OPERATORS.include?(expression.operator) && aggregate_equality_type?(expression.left.type)
344
+ collect_aggregate_equality_dependencies(expression.left.type, types)
345
+ end
326
346
  when IR::Call
327
- expression.arguments.any? { |arg| expression_uses_variant_equality?(arg) }
347
+ collect_aggregate_equality_from_expression(expression.callee, types) unless expression.callee.is_a?(String)
348
+ expression.arguments.each { |argument| collect_aggregate_equality_from_expression(argument, types) }
349
+ when IR::Member
350
+ collect_aggregate_equality_from_expression(expression.receiver, types)
351
+ when IR::Index, IR::CheckedIndex, IR::CheckedSpanIndex, IR::NullableIndex, IR::NullableSpanIndex
352
+ collect_aggregate_equality_from_expression(expression.receiver, types)
353
+ collect_aggregate_equality_from_expression(expression.index, types)
354
+ when IR::Unary
355
+ collect_aggregate_equality_from_expression(expression.operand, types)
356
+ when IR::Conditional
357
+ collect_aggregate_equality_from_expression(expression.condition, types)
358
+ collect_aggregate_equality_from_expression(expression.then_expression, types)
359
+ collect_aggregate_equality_from_expression(expression.else_expression, types)
360
+ when IR::ReinterpretExpr, IR::Cast, IR::AddressOf
361
+ collect_aggregate_equality_from_expression(expression.expression, types)
362
+ when IR::AggregateLiteral
363
+ expression.fields.each { |field| collect_aggregate_equality_from_expression(field.value, types) }
364
+ when IR::ArrayLiteral
365
+ expression.elements.each { |element| collect_aggregate_equality_from_expression(element, types) }
366
+ when IR::VariantLiteral
367
+ expression.fields.each { |field| collect_aggregate_equality_from_expression(field.value, types) }
368
+ end
369
+ false
370
+ end
371
+
372
+ def collect_aggregate_equality_dependencies(type, types)
373
+ return if types.include?(type)
374
+
375
+ case type
376
+ when Types::Variant
377
+ types << type
378
+ type.arm_names.each do |arm_name|
379
+ (type.arm(arm_name) || {}).each_value { |field_type| collect_value_field_equality_dependencies(field_type, types) }
380
+ end
381
+ when Types::VariantArmPayload
382
+ types << type
383
+ arm_fields = type.variant_type.arm(type.arm_name) || {}
384
+ arm_fields.each_value { |field_type| collect_value_field_equality_dependencies(field_type, types) }
385
+ when Types::Struct
386
+ return unless struct_equality_type?(type)
387
+
388
+ types << type
389
+ type.fields.each_value { |field_type| collect_value_field_equality_dependencies(field_type, types) }
390
+ end
391
+ end
392
+
393
+ def collect_value_field_equality_dependencies(type, types)
394
+ case type
395
+ when Types::Nullable
396
+ collect_value_field_equality_dependencies(type.base, types)
397
+ when Types::Struct, Types::Variant
398
+ collect_aggregate_equality_dependencies(type, types)
399
+ when Types::VariantArmPayload
400
+ collect_aggregate_equality_dependencies(type, types)
401
+ when Types::GenericInstance
402
+ collect_value_field_equality_dependencies(array_element_type(type), types) if array_type?(type)
403
+ end
404
+ end
405
+
406
+ def aggregate_equality_needs_string_view?
407
+ aggregate_equality_types.any? { |type| aggregate_contains_string_view?(type) }
408
+ end
409
+
410
+ def aggregate_contains_string_view?(type, visiting = nil)
411
+ return false unless type
412
+ return false if visiting&.key?(type)
413
+
414
+ visiting = (visiting || {}).merge(type => true)
415
+ case type
416
+ when Types::StringView
417
+ true
418
+ when Types::Struct
419
+ type.fields.any? { |_name, field_type| aggregate_contains_string_view?(field_type, visiting) }
420
+ when Types::Variant
421
+ type.arm_names.any? { |arm_name| (type.arm(arm_name) || {}).any? { |_field_name, field_type| aggregate_contains_string_view?(field_type, visiting) } }
422
+ when Types::Nullable
423
+ aggregate_contains_string_view?(type.base, visiting)
424
+ when Types::GenericInstance
425
+ type.arguments.any? { |argument| aggregate_contains_string_view?(argument, visiting) }
328
426
  else
329
427
  false
330
428
  end
@@ -42,8 +42,32 @@ module MilkTea
42
42
  ]
43
43
  end
44
44
 
45
+ def emit_str_concat_helper
46
+ [
47
+ "#define MT_STR_CONCAT_BUF_SIZE 65536",
48
+ "",
49
+ "static _Thread_local char mt_str_concat_buf[MT_STR_CONCAT_BUF_SIZE];",
50
+ "static _Thread_local uintptr_t mt_str_concat_offset = 0;",
51
+ "",
52
+ "static mt_str mt_str_concat(mt_str a, mt_str b) {",
53
+ "#{INDENT}uintptr_t total = a.len + b.len;",
54
+ "#{INDENT}if (mt_str_concat_offset + total > MT_STR_CONCAT_BUF_SIZE) {",
55
+ "#{INDENT * 2}mt_str_concat_offset = 0;",
56
+ "#{INDENT}}",
57
+ "#{INDENT}char* buf = mt_str_concat_buf + mt_str_concat_offset;",
58
+ "#{INDENT}if (a.len > 0) memcpy(buf, a.data, a.len);",
59
+ "#{INDENT}if (b.len > 0) memcpy(buf + a.len, b.data, b.len);",
60
+ "#{INDENT}mt_str_concat_offset += total;",
61
+ "#{INDENT}return (mt_str){ .data = buf, .len = total };",
62
+ "}",
63
+ ]
64
+ end
65
+
45
66
  def emit_variant_equality_helpers
46
- emitted_aggregate_variants.flat_map { |variant_decl| emit_variant_equality_helper(variant_decl) }
67
+ variant_decls_by_linkage = (emitted_aggregate_variants + collect_generic_variant_decls).each_with_object({}) { |decl, map| map[decl.linkage_name] = decl }
68
+ variant_equality_types
69
+ .filter_map { |type| variant_decls_by_linkage[named_type_c_name(type)] }
70
+ .flat_map { |variant_decl| emit_variant_equality_helper(variant_decl) }
47
71
  end
48
72
 
49
73
  def emit_variant_equality_helper(variant_decl)
@@ -58,25 +82,14 @@ module MilkTea
58
82
  lines << "#{INDENT * 3}return true;"
59
83
  else
60
84
  arm.fields.each do |field|
61
- field_type = field.type
62
- left_expr = "left.data.#{sanitize_c_identifier(arm.name)}.#{sanitize_c_identifier(field.name)}"
63
- right_expr = "right.data.#{sanitize_c_identifier(arm.name)}.#{sanitize_c_identifier(field.name)}"
64
- if field_type.is_a?(Types::StringView)
65
- lines << "#{INDENT * 3}if (!mt_str_equal(#{left_expr}, #{right_expr})) return false;"
66
- elsif field_type.is_a?(Types::Variant)
67
- lines << "#{INDENT * 3}if (!mt_variant_eq_#{named_type_c_name(field_type)}(#{left_expr}, #{right_expr})) return false;"
68
- elsif field_type.is_a?(Types::Nullable)
69
- if c_backend_pointer_like_type?(field_type.base)
70
- lines << "#{INDENT * 3}if (#{left_expr} != #{right_expr}) return false;"
71
- else
72
- lines << "#{INDENT * 3}if (#{left_expr}.has_value != #{right_expr}.has_value) return false;"
73
- lines << "#{INDENT * 3}if (#{left_expr}.has_value && #{left_expr}.value != #{right_expr}.value) return false;"
74
- end
75
- elsif field_type.is_a?(Types::Primitive) || field_type.is_a?(Types::EnumBase)
76
- lines << "#{INDENT * 3}if (#{left_expr} != #{right_expr}) return false;"
77
- else
78
- lines << "#{INDENT * 3}if (#{left_expr} != #{right_expr}) return false;"
79
- end
85
+ emit_field_equality_guards(
86
+ lines,
87
+ "left.data.#{sanitize_c_identifier(arm.name)}.#{sanitize_c_identifier(field.name)}",
88
+ "right.data.#{sanitize_c_identifier(arm.name)}.#{sanitize_c_identifier(field.name)}",
89
+ field.type,
90
+ outer_c:,
91
+ indent: 3,
92
+ )
80
93
  end
81
94
  lines << "#{INDENT * 3}return true;"
82
95
  end
@@ -88,6 +101,83 @@ module MilkTea
88
101
  lines
89
102
  end
90
103
 
104
+ def emit_struct_equality_helpers
105
+ struct_decls_by_linkage = (emitted_aggregate_structs + collect_generic_struct_decls).each_with_object({}) { |decl, map| map[decl.linkage_name] = decl }
106
+ struct_equality_types
107
+ .filter_map { |type| type.is_a?(Types::VariantArmPayload) ? type : struct_decls_by_linkage[named_type_c_name(type)] }
108
+ .flat_map { |decl_or_type| emit_struct_equality_helper(decl_or_type) }
109
+ end
110
+
111
+ def emit_struct_equality_helper(struct_decl_or_type)
112
+ if struct_decl_or_type.is_a?(IR::StructDecl)
113
+ outer_c = struct_decl_or_type.linkage_name
114
+ fields = struct_decl_or_type.fields
115
+ else
116
+ payload = struct_decl_or_type
117
+ outer_c = named_type_c_name(payload)
118
+ arm_fields = payload.variant_type.arm(payload.arm_name) || {}
119
+ fields = arm_fields.map { |name, field_type| IR::Field.new(name:, type: field_type) }
120
+ end
121
+
122
+ lines = ["static bool mt_struct_eq_#{outer_c}(struct #{outer_c} left, struct #{outer_c} right) {"]
123
+ fields.each do |field|
124
+ emit_field_equality_guards(
125
+ lines,
126
+ "left.#{sanitize_c_identifier(field.name)}",
127
+ "right.#{sanitize_c_identifier(field.name)}",
128
+ field.type,
129
+ outer_c:,
130
+ )
131
+ end
132
+ lines << "#{INDENT}return true;"
133
+ lines << "}"
134
+ lines
135
+ end
136
+
137
+ def emit_field_equality_guards(lines, left, right, type, outer_c: nil, indent: 1)
138
+ pad = INDENT * indent
139
+ case type
140
+ when Types::StringView
141
+ lines << "#{pad}if (!mt_str_equal(#{left}, #{right})) return false;"
142
+ when Types::Variant
143
+ if aggregate_field_creates_cycle?(type, outer_c)
144
+ lines << "#{pad}if (#{left} != #{right}) return false;"
145
+ else
146
+ lines << "#{pad}if (!mt_variant_eq_#{named_type_c_name(type)}(#{left}, #{right})) return false;"
147
+ end
148
+ when Types::VariantArmPayload
149
+ if aggregate_field_creates_cycle?(type.variant_type, outer_c)
150
+ lines << "#{pad}if (#{left} != #{right}) return false;"
151
+ else
152
+ lines << "#{pad}if (!mt_struct_eq_#{named_type_c_name(type)}(#{left}, #{right})) return false;"
153
+ end
154
+ when Types::Struct
155
+ if aggregate_field_creates_cycle?(type, outer_c)
156
+ lines << "#{pad}if (#{left} != #{right}) return false;"
157
+ else
158
+ lines << "#{pad}if (!mt_struct_eq_#{named_type_c_name(type)}(#{left}, #{right})) return false;"
159
+ end
160
+ when Types::Nullable
161
+ if c_backend_pointer_like_type?(type.base)
162
+ lines << "#{pad}if (#{left} != #{right}) return false;"
163
+ else
164
+ lines << "#{pad}if (#{left}.has_value != #{right}.has_value) return false;"
165
+ lines << "#{pad}if (#{left}.has_value) {"
166
+ emit_field_equality_guards(lines, "#{left}.value", "#{right}.value", type.base, outer_c:, indent: indent + 1)
167
+ lines << "#{pad}}"
168
+ end
169
+ else
170
+ if array_type?(type)
171
+ count = array_length(type)
172
+ lines << "#{pad}for (uintptr_t index = 0; index < #{count}; index++) {"
173
+ emit_field_equality_guards(lines, "#{left}[index]", "#{right}[index]", array_element_type(type), outer_c:, indent: indent + 1)
174
+ lines << "#{pad}}"
175
+ else
176
+ lines << "#{pad}if (#{left} != #{right}) return false;"
177
+ end
178
+ end
179
+ end
180
+
91
181
  def emit_async_memory_helpers
92
182
  [
93
183
  "#define MT_ASYNC_HEADER_SIZE (sizeof(uint64_t) + sizeof(uintptr_t))",