mt-lang 0.3.30 → 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: d6d13cde9fca875b324ff3e0cb3b892c371e2445ac5e061d138bd0dc8b284b88
4
- data.tar.gz: 2a91eedc02120e0d042bc6bafbfc783a4eefb5bbc44c34940e1ee9be6917405e
3
+ metadata.gz: 16833c6b95c7e580f12199a381802f47ac628b7252f4bc1647714098cbd4f824
4
+ data.tar.gz: 296b0547096ceee9d394edc13df6f042e2b8ed6d42c98b4745e1b39885407b85
5
5
  SHA512:
6
- metadata.gz: 478dc59b852c703b5cf63ce3cd77a92d6de305424c96cfa7dbeda97ed1882f2d8c52f5770893dc99a3077cb1599cbf0602c6e54b773f219ce859b8b9275c7258
7
- data.tar.gz: d221d15fe3c3192ee0b10b61101f5234e8b92830f878051c6e2641aca334b7d165c9668bf410a727b4964f02d45805322044470a9e406b971ab7508650d60a00
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`. The `+` operator concatenates `str` values: `"hello" + " " + "world"` produces `"hello world"`. For loops or repeated concatenation, prefer `string.String`.
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`
@@ -106,7 +106,7 @@ Common punctuation and operators:
106
106
  - delimiters: `(` `)` `[` `]`
107
107
  - access and separators: `:` `,` `.`
108
108
  - type markers: `->` `?`
109
- - arithmetic: `+ - * / %` (additionally, `+` on `str` concatenates)
109
+ - arithmetic: `+ - * / %`
110
110
  - bitwise: `~ & | ^ << >>`
111
111
  - comparison: `== != < <= > >=`
112
112
  - assignment: `= += -= *= /= %= &= |= ^= <<= >>=`
@@ -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.
@@ -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
@@ -961,7 +961,7 @@ let total = (
961
961
  <tr><td>Delimiters</td><td><code>( ) [ ]</code></td></tr>
962
962
  <tr><td>Separators / Access</td><td><code>: , .</code></td></tr>
963
963
  <tr><td>Type markers</td><td><code>-&gt;</code> <code>?</code></td></tr>
964
- <tr><td>Arithmetic</td><td><code>+ - * / %</code> (the <code>+</code> operator on <code>str</code> concatenates, allocating a new heap-backed string)</td></tr>
964
+ <tr><td>Arithmetic</td><td><code>+ - * / %</code></td></tr>
965
965
  <tr><td>Bitwise</td><td><code>~ &amp; | ^ &lt;&lt; &gt;&gt;</code></td></tr>
966
966
  <tr><td>Comparison</td><td><code>== != &lt; &lt;= &gt; &gt;=</code></td></tr>
967
967
  <tr><td>Assignment</td><td><code>= += -= *= /= %= &amp;= |= ^= &lt;&lt;= &gt;&gt;=</code></td></tr>
@@ -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">
@@ -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>
@@ -755,13 +755,15 @@ Explicit specialization arguments may be type references like `bytes_for[int](4)
755
755
 
756
756
  Built-in operators should match familiar C behavior where possible:
757
757
 
758
- - arithmetic: `+ - * / %` (the `+` operator on `str` concatenates, allocating a new heap-backed string)
758
+ - arithmetic: `+ - * / %`
759
759
  - comparison: `== != < <= > >=`
760
760
  - boolean: `and or not`
761
761
  - bitwise: `& | ^ ~ << >>`
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. The `+` operator concatenates `str` values: `"hello" + " " + "world"` produces `"hello world"`. Each `+` allocates a new heap-backed `str`; for loops or repeated concatenation, prefer `string.String` for amortized performance.
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
@@ -1015,9 +1069,11 @@ Rules:
1015
1069
  8. `==`, `!=`
1016
1070
  9. `<`, `<=`, `>`, `>=`
1017
1071
  10. `<<`, `>>`
1018
- 11. `+`, `-` (additionally, `+` on `str` concatenates; each `+` allocates a new heap-backed `str`)
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.30"
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)
@@ -298,41 +298,131 @@ module MilkTea
298
298
  end
299
299
 
300
300
  def uses_variant_equality_helper?
301
- emitted_functions.any? { |function| function_uses_variant_equality?(function) }
301
+ !variant_equality_types.empty?
302
302
  end
303
303
 
304
- def function_uses_variant_equality?(function)
305
- function.body.any? { |statement| statement_uses_variant_equality?(statement) }
304
+ def uses_struct_equality_helper?
305
+ !struct_equality_types.empty?
306
306
  end
307
307
 
308
- def statement_uses_variant_equality?(statement)
309
- case statement
310
- when IR::LocalDecl
311
- expression_uses_variant_equality?(statement.value)
312
- when IR::ExpressionStmt
313
- expression_uses_variant_equality?(statement.expression)
314
- when IR::ReturnStmt
315
- statement.value && expression_uses_variant_equality?(statement.value)
316
- when IR::Assignment
317
- expression_uses_variant_equality?(statement.value)
318
- when IR::IfStmt
319
- expression_uses_variant_equality?(statement.condition)
320
- when IR::WhileStmt
321
- expression_uses_variant_equality?(statement.condition)
322
- else
323
- 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
324
323
  end
325
324
  end
326
325
 
327
- def expression_uses_variant_equality?(expression)
328
- 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
329
+
330
+ def variant_equality_types
331
+ @variant_equality_types ||= aggregate_equality_types.select { |type| type.is_a?(Types::Variant) }
332
+ end
329
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)
330
339
  case expression
331
340
  when IR::Binary
332
- EQUALITY_OPERATORS.include?(expression.operator) &&
333
- (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
334
346
  when IR::Call
335
- 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) }
336
426
  else
337
427
  false
338
428
  end
@@ -64,7 +64,10 @@ module MilkTea
64
64
  end
65
65
 
66
66
  def emit_variant_equality_helpers
67
- 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) }
68
71
  end
69
72
 
70
73
  def emit_variant_equality_helper(variant_decl)
@@ -79,25 +82,14 @@ module MilkTea
79
82
  lines << "#{INDENT * 3}return true;"
80
83
  else
81
84
  arm.fields.each do |field|
82
- field_type = field.type
83
- left_expr = "left.data.#{sanitize_c_identifier(arm.name)}.#{sanitize_c_identifier(field.name)}"
84
- right_expr = "right.data.#{sanitize_c_identifier(arm.name)}.#{sanitize_c_identifier(field.name)}"
85
- if field_type.is_a?(Types::StringView)
86
- lines << "#{INDENT * 3}if (!mt_str_equal(#{left_expr}, #{right_expr})) return false;"
87
- elsif field_type.is_a?(Types::Variant)
88
- lines << "#{INDENT * 3}if (!mt_variant_eq_#{named_type_c_name(field_type)}(#{left_expr}, #{right_expr})) return false;"
89
- elsif field_type.is_a?(Types::Nullable)
90
- if c_backend_pointer_like_type?(field_type.base)
91
- lines << "#{INDENT * 3}if (#{left_expr} != #{right_expr}) return false;"
92
- else
93
- lines << "#{INDENT * 3}if (#{left_expr}.has_value != #{right_expr}.has_value) return false;"
94
- lines << "#{INDENT * 3}if (#{left_expr}.has_value && #{left_expr}.value != #{right_expr}.value) return false;"
95
- end
96
- elsif field_type.is_a?(Types::Primitive) || field_type.is_a?(Types::EnumBase)
97
- lines << "#{INDENT * 3}if (#{left_expr} != #{right_expr}) return false;"
98
- else
99
- lines << "#{INDENT * 3}if (#{left_expr} != #{right_expr}) return false;"
100
- 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
+ )
101
93
  end
102
94
  lines << "#{INDENT * 3}return true;"
103
95
  end
@@ -109,6 +101,83 @@ module MilkTea
109
101
  lines
110
102
  end
111
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
+
112
181
  def emit_async_memory_helpers
113
182
  [
114
183
  "#define MT_ASYNC_HEADER_SIZE (sizeof(uint64_t) + sizeof(uintptr_t))",
@@ -236,17 +236,45 @@ module MilkTea
236
236
  lines << ""
237
237
  end
238
238
 
239
+ if uses_variant_equality_helper? || uses_struct_equality_helper?
240
+ lines.concat(emit_aggregate_equality_forward_declarations)
241
+ lines << ""
242
+ end
239
243
  if uses_variant_equality_helper?
240
244
  unless uses_str_equality_helper?
241
- variant_needs_str_eq = emitted_aggregate_variants.any? { |v| v.arms.any? { |a| a.fields.any? { |f| f.type.is_a?(Types::StringView) } } }
242
- if variant_needs_str_eq
243
- lines.concat(emit_string_type) unless uses_string_view?
244
- lines.concat(emit_str_equality_helper)
245
- end
245
+ emit_str_equality_support(lines) if aggregate_equality_needs_string_view?
246
246
  end
247
247
  lines.concat(emit_variant_equality_helpers)
248
248
  lines << ""
249
249
  end
250
+ if uses_struct_equality_helper?
251
+ unless uses_str_equality_helper?
252
+ emit_str_equality_support(lines) if aggregate_equality_needs_string_view?
253
+ end
254
+ lines.concat(emit_struct_equality_helpers)
255
+ lines << ""
256
+ end
257
+ end
258
+
259
+ def emit_aggregate_equality_forward_declarations
260
+ lines = []
261
+ struct_equality_types.each do |type|
262
+ c = named_type_c_name(type)
263
+ lines << "static bool mt_struct_eq_#{c}(struct #{c} left, struct #{c} right);"
264
+ end
265
+ variant_equality_types.each do |type|
266
+ c = named_type_c_name(type)
267
+ lines << "static bool mt_variant_eq_#{c}(struct #{c} left, struct #{c} right);"
268
+ end
269
+ lines
270
+ end
271
+
272
+ def emit_str_equality_support(lines)
273
+ return if @emitted_aggregate_str_equality_support
274
+
275
+ @emitted_aggregate_str_equality_support = true
276
+ lines.concat(emit_string_type) unless uses_string_view?
277
+ lines.concat(emit_str_equality_helper)
250
278
  end
251
279
 
252
280
  def emit_function_forward_declarations(lines)
@@ -31,10 +31,35 @@ module MilkTea
31
31
  return left == right if left.is_a?(String) && right.is_a?(String)
32
32
  return left == right if boolean_value?(left) && boolean_value?(right)
33
33
  return left == right if left.is_a?(Types::Base) && right.is_a?(Types::Base)
34
+ return struct_equality_result(left, right) if left.is_a?(Hash) && right.is_a?(Hash)
35
+ return variant_equality_result(left, right) if left.is_a?(VariantValue) && right.is_a?(VariantValue)
34
36
 
35
37
  nil
36
38
  end
37
39
 
40
+ # Const-time struct values are represented as {field_name => value} hashes;
41
+ # compare them field by field, mirroring the runtime struct == semantics.
42
+ def self.struct_equality_result(left, right)
43
+ return nil unless left.is_a?(Hash) && right.is_a?(Hash)
44
+ return nil unless left.keys.sort == right.keys.sort
45
+
46
+ left.each do |name, value|
47
+ field_result = equality_result(value, right[name])
48
+ return nil if field_result.nil?
49
+ return false if field_result == false
50
+ end
51
+ true
52
+ end
53
+
54
+ # Const-time variant values; fields mirror the runtime arm payload layout.
55
+ VariantValue = Data.define(:arm, :fields)
56
+
57
+ def self.variant_equality_result(left, right)
58
+ return false unless left.arm == right.arm
59
+
60
+ struct_equality_result(left.fields, right.fields)
61
+ end
62
+
38
63
  def self.boolean_value?(value)
39
64
  value == true || value == false
40
65
  end
@@ -78,6 +78,14 @@ module MilkTea
78
78
  IR::AggregateField.new(name:, value: lower_const_value_literal(field_type, field_value))
79
79
  end
80
80
  IR::AggregateLiteral.new(type:, fields:)
81
+ when CompileTime::VariantValue
82
+ arm_field_types = type.respond_to?(:arm) ? (type.arm(const_value.arm) || {}) : {}
83
+ fields = const_value.fields.map do |field_name, field_value|
84
+ field_type = arm_field_types[field_name]
85
+ raise LoweringError.new("constant variant arm #{const_value.arm} field #{field_name} not found in #{type}", line: 0, column: 0, path: @ctx.current_analysis_path) unless field_type
86
+ IR::AggregateField.new(name: field_name, value: lower_const_value_literal(field_type, field_value))
87
+ end
88
+ IR::VariantLiteral.new(type:, arm_name: const_value.arm, fields:)
81
89
  else
82
90
  raise LoweringError.new("unsupported const value type #{const_value.class}", line: 0, column: 0, path: @ctx.current_analysis_path)
83
91
  end
@@ -270,10 +270,19 @@ module MilkTea
270
270
 
271
271
  if expected_type.is_a?(Types::Primitive) && expected_type.integer? &&
272
272
  value_fits_integer_type?(expression.value, expected_type)
273
- expected_type
274
- else
275
- @ctx.types.fetch("int")
273
+ return expected_type
276
274
  end
275
+
276
+ # A bare literal (no suffix, no fitting expected type) picks the
277
+ # narrowest fixed-width type that holds its value: int -> long -> ulong.
278
+ # Falling through to int unconditionally emitted overflowing C
279
+ # (e.g. `int32_t x = 2147483648;`).
280
+ ["int", "long", "ulong"].each do |name|
281
+ candidate = @ctx.types.fetch(name)
282
+ return candidate if value_fits_integer_type?(expression.value, candidate)
283
+ end
284
+
285
+ raise_sema_error("integer literal #{expression.value} does not fit in any integer type", expression)
277
286
  end
278
287
 
279
288
  INTEGER_SUFFIX_TYPES = {
@@ -300,11 +309,23 @@ module MilkTea
300
309
  @ctx.types.fetch("double")
301
310
  elsif expected_type.is_a?(Types::Primitive) && expected_type.float?
302
311
  expected_type
303
- else
312
+ elsif float_literal_fits_in_float?(expression.value)
304
313
  @ctx.types.fetch("float")
314
+ else
315
+ # A bare float literal beyond float32 range would silently overflow
316
+ # to infinity in C (`float x = 1e40`); promote to double instead.
317
+ @ctx.types.fetch("double")
305
318
  end
306
319
  end
307
320
 
321
+ FLOAT32_MAX_MAGNITUDE = 3.4028234663852886e+38
322
+
323
+ def float_literal_fits_in_float?(value)
324
+ return false unless value.is_a?(Numeric) && value.finite?
325
+
326
+ value.abs <= FLOAT32_MAX_MAGNITUDE
327
+ end
328
+
308
329
  def infer_identifier(expression, scopes:, expected_type: nil)
309
330
  binding = lookup_value(expression.name, scopes)
310
331
  if binding
@@ -662,8 +683,16 @@ module MilkTea
662
683
  when "==", "!="
663
684
  unless c_natively_equality_comparable_type?(left_type) && c_natively_equality_comparable_type?(right_type)
664
685
  bad_type = c_natively_equality_comparable_type?(right_type) ? left_type : right_type
665
- if struct_instance_type?(bad_type)
666
- raise_sema_error("operator #{expression.operator} is not supported for struct type #{bad_type}; use equal[#{bad_type}](...) instead")
686
+ if bad_type.is_a?(Types::Union)
687
+ raise_sema_error("operator #{expression.operator} is not supported for union type #{bad_type}; use equal[#{bad_type}](...) instead")
688
+ elsif bad_type.is_a?(Types::Variant)
689
+ field = first_non_equality_comparable_variant_field(bad_type)
690
+ field_hint = field ? " (arm '#{field[0]}' field '#{field[1]}' of type #{field[2]} is not equality-comparable)" : ""
691
+ raise_sema_error("operator #{expression.operator} is not supported for variant type #{bad_type}#{field_hint}; use equal[#{bad_type}](...) instead")
692
+ elsif struct_instance_type?(bad_type) && !bad_type.is_a?(Types::Variant)
693
+ field = first_non_equality_comparable_field(bad_type)
694
+ field_hint = field ? " (field '#{field.first}' of type #{field.last} is not equality-comparable)" : ""
695
+ raise_sema_error("operator #{expression.operator} is not supported for struct type #{bad_type}#{field_hint}; use equal[#{bad_type}](...) instead")
667
696
  else
668
697
  raise_sema_error("operator #{expression.operator} is not supported for type #{bad_type}")
669
698
  end
@@ -902,23 +902,65 @@ module MilkTea
902
902
  type.is_a?(Types::Struct) || type.is_a?(Types::Variant)
903
903
  end
904
904
 
905
- def c_natively_equality_comparable_type?(type)
905
+ def c_natively_equality_comparable_type?(type, visiting = nil)
906
906
  return true if type.is_a?(Types::Primitive)
907
907
  return true if type.is_a?(Types::EnumBase)
908
908
  return true if type.is_a?(Types::Opaque)
909
- return true if type.is_a?(Types::Nullable)
909
+ return true if type.is_a?(Types::Nullable) && equality_comparable_field_type?(type.base, visiting)
910
910
  return true if type.is_a?(Types::Null)
911
911
  return true if type.is_a?(Types::Function)
912
912
  return true if type.is_a?(Types::Error)
913
913
  return true if type.is_a?(Types::StringView)
914
914
  return true if pointer_type?(type)
915
915
  return true if ref_type?(type)
916
- return true if type.is_a?(Types::Variant)
916
+ return true if type.is_a?(Types::Variant) && equality_comparable_variant_type?(type, visiting)
917
917
  return true if type.is_a?(Types::VariantArmPayload)
918
+ return true if equality_comparable_struct_type?(type, visiting)
919
+
920
+ false
921
+ end
922
+
923
+ # A struct value is equality-comparable with ==/!= when every field is
924
+ # itself equality-comparable. Untagged unions are excluded because their
925
+ # active-field comparison is ambiguous.
926
+ def equality_comparable_struct_type?(type, visiting = nil)
927
+ type.is_a?(Types::Struct) && !type.is_a?(Types::Union) &&
928
+ type.fields.all? { |_name, field_type| equality_comparable_field_type?(field_type, visiting) }
929
+ end
930
+
931
+ def equality_comparable_field_type?(type, visiting = nil)
932
+ return true if c_natively_equality_comparable_type?(type, visiting)
933
+ return true if array_type?(type) && equality_comparable_field_type?(array_element_type(type), visiting)
918
934
 
919
935
  false
920
936
  end
921
937
 
938
+ # A variant is equality-comparable when every arm payload field is itself
939
+ # equality-comparable. A variant that reaches itself through value fields
940
+ # (a recursive/cyclic variant) is comparable too: the C backend embeds
941
+ # cyclic fields as pointers, and those fields compare by pointer identity.
942
+ def equality_comparable_variant_type?(type, visiting = nil)
943
+ return true if visiting&.key?(type)
944
+
945
+ visiting = (visiting || {}).merge(type => true)
946
+ type.arm_names.all? do |arm_name|
947
+ (type.arm(arm_name) || {}).all? { |_field_name, field_type| equality_comparable_field_type?(field_type, visiting) }
948
+ end
949
+ end
950
+
951
+ def first_non_equality_comparable_field(struct_type)
952
+ struct_type.fields.find { |_name, field_type| !equality_comparable_field_type?(field_type) }
953
+ end
954
+
955
+ def first_non_equality_comparable_variant_field(variant_type)
956
+ variant_type.arm_names.each do |arm_name|
957
+ (variant_type.arm(arm_name) || {}).each do |field_name, field_type|
958
+ return [arm_name, field_name, field_type] unless equality_comparable_field_type?(field_type)
959
+ end
960
+ end
961
+ nil
962
+ end
963
+
922
964
  def collection_loop_type(type)
923
965
  super
924
966
  end
@@ -301,7 +301,12 @@ module MilkTea
301
301
  end
302
302
 
303
303
  if (receiver_type = resolve_type_expression(member_access_expression.receiver))
304
- next resolve_enum_member_const_value(receiver_type, member_access_expression.member)
304
+ if receiver_type.is_a?(Types::EnumBase)
305
+ next resolve_enum_member_const_value(receiver_type, member_access_expression.member)
306
+ end
307
+ if receiver_type.is_a?(Types::Variant)
308
+ next CompileTime::VariantValue.new(arm: member_access_expression.member, fields: {})
309
+ end
305
310
  end
306
311
 
307
312
  next unless member_access_expression.receiver.is_a?(AST::Identifier)
@@ -321,6 +326,31 @@ module MilkTea
321
326
 
322
327
  def evaluate_compile_time_call(expression, scopes: nil)
323
328
  case expression.callee
329
+ when AST::MemberAccess
330
+ if (receiver_type = resolve_type_expression(expression.callee.receiver)) && receiver_type.is_a?(Types::Variant)
331
+ arm_name = expression.callee.member
332
+ fields = {}
333
+ expression.arguments.each do |argument|
334
+ val = CompileTime.evaluate(argument.value, resolve_identifier: lambda { |id|
335
+ if scopes
336
+ binding = lookup_value(id.name, scopes)
337
+ return binding.const_value unless binding&.const_value.nil?
338
+ end
339
+ resolve_current_module_const_value(id.name)
340
+ }, resolve_member_access: lambda { |ma|
341
+ if (member_receiver_type = resolve_type_expression(ma.receiver))
342
+ next resolve_enum_member_const_value(member_receiver_type, ma.member) if member_receiver_type.is_a?(Types::EnumBase)
343
+ next CompileTime::VariantValue.new(arm: ma.member, fields: {}) if member_receiver_type.is_a?(Types::Variant)
344
+ end
345
+ nil
346
+ }, resolve_call: lambda { |inner_call|
347
+ evaluate_compile_time_call(inner_call, scopes:)
348
+ })
349
+ return nil unless val
350
+ fields[argument.name] = val
351
+ end
352
+ return CompileTime::VariantValue.new(arm: arm_name, fields: fields)
353
+ end
324
354
  when AST::Identifier
325
355
  if (struct_type = @ctx.types[expression.callee.name]) && struct_type.is_a?(Types::Struct)
326
356
  fields = {}
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.30
4
+ version: 0.3.31
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.30 installed!
627
+ Milk Tea 0.3.31 installed!
628
628
 
629
629
  System requirements:
630
630
  - A C compiler (gcc or clang) must be available on PATH