mt-lang 0.3.12 → 0.3.14
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 +4 -4
- data/README.md +3 -3
- data/docs/index.html +23 -15
- data/docs/language-design.md +9 -9
- data/docs/language-manual.md +2 -2
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/bindings/bindgen.rb +1 -1
- data/lib/milk_tea/bindings/imported_bindings.rb +1 -1
- data/lib/milk_tea/core/lowering/expressions.rb +47 -3
- data/lib/milk_tea/core/lowering/resolve.rb +26 -2
- data/lib/milk_tea/core/lowering/utils.rb +6 -1
- data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +29 -2
- data/lib/milk_tea/core/types/layout.rb +1 -1
- data/lib/milk_tea/core/{types/types.rb → types.rb} +2 -8
- data/lib/milk_tea/core.rb +1 -1
- data/lib/milk_tea/tooling/cli.rb +33 -6
- data/lib/milk_tea/tooling/sexpr_dumper.rb +342 -0
- data/lib/milk_tea/tooling/sexpr_parser.rb +420 -0
- data/lib/milk_tea/tooling.rb +2 -0
- data/std/{cell.mt → box.mt} +9 -9
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12f7a371c9cb4162851cefb9277292455904b6e0bedcba05312770f00d724481
|
|
4
|
+
data.tar.gz: 4d73b7c4ef5c45abc731edbfd6e465067230b6772669ad8952a75247cd10e2ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d434a523bf7e5678512af400a5054765d62976778d27c1d647203fa03a39145509648d82d32d56d02218da1324d66717b1eb1da75543ea513e49fcc2e9fc5410
|
|
7
|
+
data.tar.gz: d2fa42eb284fd01df9492fa772d69c46853712862dd6f8d4f1f0f20aa8c40fc996abf821763175419cb58abb2ffe2da94a750e30b8c47d7b0c9ae9c31ec75f75
|
data/README.md
CHANGED
|
@@ -236,7 +236,7 @@ Callable and `ref[...]` rules:
|
|
|
236
236
|
- External functions still cannot take `ref[...]` parameters, and ordinary functions still cannot return `ref[...]`.
|
|
237
237
|
- `proc` captures are value captures. A captured local is not a mutable alias back to the outer binding. Any storable type may be captured, including scalars, arrays, structs, and other `proc` values. Captured `proc` values participate in the ref-counted lifecycle: the capturing proc retains the captured proc on creation and releases it when the env is freed.
|
|
238
238
|
- `ref[T]` values are not capturable by design since they are non-owning.
|
|
239
|
-
- Shared mutable proc state should use explicit storage such as `std.
|
|
239
|
+
- Shared mutable proc state should use explicit storage such as `std.box.alloc[T](...)` or other explicit pointer-backed state, not implicit mutable capture.
|
|
240
240
|
|
|
241
241
|
## 5. Data Declarations
|
|
242
242
|
|
|
@@ -902,7 +902,7 @@ Core modules in `std/`:
|
|
|
902
902
|
|
|
903
903
|
**Compression**: `std.gzip`, `std.tar`
|
|
904
904
|
|
|
905
|
-
**Other**: `std.bytes`, `std.ctype`, `std.asset_pack`, `std.
|
|
905
|
+
**Other**: `std.bytes`, `std.ctype`, `std.asset_pack`, `std.box`
|
|
906
906
|
|
|
907
907
|
See module source for full method surface. Iterator forms:
|
|
908
908
|
- Pointer-returning (`next() -> nullable ptr[T]`): `Vec`, `Deque`, `BinaryHeap`/`PriorityQueue`/`OrderedSet` (read-only), `OrderedMap.keys`/`Map.keys`/`Set`/`LinkedMap.keys`/`LinkedSet`/`Counter.keys`/`MultiSet.values`, `Queue`/`Stack` (mutable), `RingBuffer`, `SparseSet`
|
|
@@ -955,7 +955,7 @@ Heredoc notes:
|
|
|
955
955
|
- Conditions must be `bool`.
|
|
956
956
|
- No truthy or falsy coercion.
|
|
957
957
|
- Outside external-call boundaries, enum and flags values do not implicitly coerce to their backing integer types.
|
|
958
|
-
- Mixed signed and unsigned integer arithmetic requires an explicit cast.
|
|
958
|
+
- Mixed signed and unsigned integer arithmetic promotes to the narrowest signed type that holds both ranges (e.g. `ubyte + int` is `int`, `int + uint` is `long`); mixing with a 64-bit unsigned type such as `ulong` or `ptr_uint` requires an explicit cast.
|
|
959
959
|
- Non-widening integer conversions (narrowing, signed → unsigned) require an explicit cast. Lossless widening conversions (same signedness to wider or equal width, unsigned → wider signed) are implicit.
|
|
960
960
|
- `%` requires integer-compatible operands.
|
|
961
961
|
- Bitwise operators require matching integer or flags types.
|
data/docs/index.html
CHANGED
|
@@ -311,7 +311,7 @@ function main() -> int:
|
|
|
311
311
|
<div class="table-wrap">
|
|
312
312
|
<table class="attr-table">
|
|
313
313
|
<tr><th>Principle</th><th>Description</th></tr>
|
|
314
|
-
<tr><td><strong>What you see is what runs</strong></td><td>
|
|
314
|
+
<tr><td><strong>What you see is what runs</strong></td><td>Ordinary values have no hidden allocation or dispatch. Runtime interface dispatch and closure environments are explicit language features with their own costs.</td></tr>
|
|
315
315
|
<tr><td><strong>C is the ABI ground truth</strong></td><td>Structs, unions, pointers, and calling conventions map directly without lossy translation.</td></tr>
|
|
316
316
|
<tr><td><strong>Safe by default, unsafe by choice</strong></td><td>Pointer operations require <code>unsafe</code>. Indexing is bounds-checked. Conversions are explicit.</td></tr>
|
|
317
317
|
<tr><td><strong>Data-oriented first</strong></td><td>Plain structs, arrays, spans, pools, and arenas matter more than elaborate object systems.</td></tr>
|
|
@@ -355,7 +355,7 @@ function main() -> int:
|
|
|
355
355
|
<!-- ────────────────────────────────────────────────────────────────────────── -->
|
|
356
356
|
<section id="generated-c">
|
|
357
357
|
<h2>Generated C</h2>
|
|
358
|
-
<p>Milk Tea compiles to readable C that mirrors the source structure.
|
|
358
|
+
<p>Milk Tea compiles to readable C that mirrors the source structure. Ordinary value operations do not introduce hidden heap traffic or dispatch; runtime interface values and closure environments remain explicit language features. The output is designed for a human to debug, diff, and ship.</p>
|
|
359
359
|
|
|
360
360
|
<div class="code-wrap">
|
|
361
361
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
@@ -466,7 +466,7 @@ variant Action:
|
|
|
466
466
|
heal(amount: int)
|
|
467
467
|
idle
|
|
468
468
|
|
|
469
|
-
## ── Interfaces (nominal contracts
|
|
469
|
+
## ── Interfaces (nominal contracts; dyn provides explicit dispatch) ───
|
|
470
470
|
public interface Damageable:
|
|
471
471
|
editable function take_damage(amount: int) -> void
|
|
472
472
|
function is_alive() -> bool
|
|
@@ -680,7 +680,7 @@ function main() -> int:
|
|
|
680
680
|
break
|
|
681
681
|
io.print_line(f"value #{v}")
|
|
682
682
|
|
|
683
|
-
##
|
|
683
|
+
## Lockstep for over arrays/spans (lengths must match)
|
|
684
684
|
let xs = array[float, 3](1.0, 2.0, 3.0)
|
|
685
685
|
let ys = array[float, 3](4.0, 5.0, 6.0)
|
|
686
686
|
for x, y in xs, ys:
|
|
@@ -840,7 +840,7 @@ function main() -> int:
|
|
|
840
840
|
<p>Milk Tea source files use the <code class="code-inline">.mt</code> extension. A file is either an <strong>ordinary</strong> source file or an <strong>external</strong> file for raw ABI bindings. There are no wildcard imports — every module must be imported by name.</p>
|
|
841
841
|
|
|
842
842
|
<h3>Ordinary Files</h3>
|
|
843
|
-
<p>The normal source form. No module header; module identity is inferred from the file path relative to <code class="code-inline">package.source_root</code>. <code class="code-inline">import</code> statements appear only at the top.</p>
|
|
843
|
+
<p>The normal source form. No module header; module identity is inferred from the file path relative to <code class="code-inline">package.source_root</code>. <code class="code-inline">import</code> statements appear only at the top. Circular module imports are supported through forward-declaration bindings and two-pass checking.</p>
|
|
844
844
|
<div class="code-wrap">
|
|
845
845
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
846
846
|
<pre><code>import std.math as math
|
|
@@ -1184,7 +1184,7 @@ static_assert(MAX_ENTITIES > 0, "need at least one entity")</code></pre>
|
|
|
1184
1184
|
<h2>Interfaces & Methods</h2>
|
|
1185
1185
|
|
|
1186
1186
|
<h3>Interfaces</h3>
|
|
1187
|
-
<p>Explicit nominal method-set contracts for static polymorphism. No inheritance
|
|
1187
|
+
<p>Explicit nominal method-set contracts for static polymorphism. No inheritance or hidden dispatch; runtime polymorphism is explicit through <code>dyn[I]</code>.</p>
|
|
1188
1188
|
<div class="code-wrap">
|
|
1189
1189
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1190
1190
|
<pre><code>public interface Damageable:
|
|
@@ -1304,7 +1304,7 @@ let handle = null[ptr[char]]</code></pre>
|
|
|
1304
1304
|
</div>
|
|
1305
1305
|
|
|
1306
1306
|
<h3>Memory Model</h3>
|
|
1307
|
-
<p>Milk Tea follows <strong>value semantics by default</strong>. Scalars, structs, and fixed arrays copy by value.
|
|
1307
|
+
<p>Milk Tea follows <strong>value semantics by default</strong>. Scalars, structs, and fixed arrays copy by value. Ordinary values do not carry hidden heap boxes or reference counts; captured <code>proc</code> values may allocate ref-counted closure environments as part of their lifecycle. User-owned heap allocation remains explicit and allocator-driven:</p>
|
|
1308
1308
|
<div class="table-wrap">
|
|
1309
1309
|
<table class="attr-table">
|
|
1310
1310
|
<tr><th>Module</th><th>Use Case</th></tr>
|
|
@@ -1450,8 +1450,8 @@ for item in items:
|
|
|
1450
1450
|
process(item)</code></pre>
|
|
1451
1451
|
</div>
|
|
1452
1452
|
|
|
1453
|
-
<h3>for (
|
|
1454
|
-
<p>Arrays and spans only
|
|
1453
|
+
<h3>for (Lockstep)</h3>
|
|
1454
|
+
<p>This is the lockstep multi-iterable form, distinct from <code>parallel for</code>. Arrays and spans only; lengths must match.</p>
|
|
1455
1455
|
<div class="code-wrap">
|
|
1456
1456
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1457
1457
|
<pre><code>for entity, position, velocity in entities, positions, velocities:
|
|
@@ -1628,7 +1628,7 @@ unsafe:
|
|
|
1628
1628
|
<h2>Compile-Time Control Flow</h2>
|
|
1629
1629
|
|
|
1630
1630
|
<h3>when</h3>
|
|
1631
|
-
<p>Evaluates discriminant at compile time; only the chosen branch is type-checked and emitted.</p>
|
|
1631
|
+
<p>Evaluates discriminant at compile time; only the chosen branch is type-checked and emitted. <code>when</code> may also appear at module level to conditionally include declarations, imports, or type definitions.</p>
|
|
1632
1632
|
<div class="code-wrap">
|
|
1633
1633
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1634
1634
|
<pre><code>when TARGET_OS:
|
|
@@ -2012,7 +2012,7 @@ let r = aio.result(task)</code></pre>
|
|
|
2012
2012
|
<!-- ────────────────────────────────────────────────────────────────────────── -->
|
|
2013
2013
|
<section id="concurrency">
|
|
2014
2014
|
<h2>Concurrency</h2>
|
|
2015
|
-
<p>Milk Tea has first-class compiler support for multithreading using real OS threads (libuv).
|
|
2015
|
+
<p>Milk Tea has first-class compiler support for multithreading using real OS threads (libuv). <code>parallel for</code> and <code>parallel:</code> are structured barriers; <code>detach</code> continues asynchronously until an explicit <code>gather</code>. There is no GC interaction or implicit fire-and-forget cleanup.</p>
|
|
2016
2016
|
|
|
2017
2017
|
<h3>Parallel For (data-parallel)</h3>
|
|
2018
2018
|
<p>Dispatches loop iterations across CPU cores. Each iteration writes to its own index range — the bread and butter of data-oriented game programming.</p>
|
|
@@ -2245,7 +2245,7 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
|
|
|
2245
2245
|
<tr><td><code>mtc lower <path></code></td><td>Print lowered IR</td></tr>
|
|
2246
2246
|
<tr><td><code>mtc debug <file.mt></code></td><td>Print debug info (tokens, AST, facts, bindings, diagnostics)</td></tr>
|
|
2247
2247
|
<tr><td><code>mtc emit-c <path></code></td><td>Emit generated C to stdout</td></tr>
|
|
2248
|
-
<tr><td><code>mtc format <path></code></td><td>Format
|
|
2248
|
+
<tr><td><code>mtc format <path></code></td><td>Format source to stdout (<code>--write</code> rewrites in place, <code>--check</code> verifies)</td></tr>
|
|
2249
2249
|
<tr><td><code>mtc lint <path></code></td><td>Run linter (<code>--fix</code>, <code>--select</code>, <code>--ignore</code>)</td></tr>
|
|
2250
2250
|
<tr><td><code>mtc test <path></code></td><td>Discover and run <code>@[test]</code> functions</td></tr>
|
|
2251
2251
|
<tr><td><code>mtc new <name></code></td><td>Scaffold a new package (<code>package.toml</code> + <code>src/main.mt</code>)</td></tr>
|
|
@@ -2416,9 +2416,17 @@ import std.math # intentionally available for downstream</code></pre>
|
|
|
2416
2416
|
|
|
2417
2417
|
<h3>Event Restrictions</h3>
|
|
2418
2418
|
<ul style="margin-left:1.25rem;margin-bottom:1rem">
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2419
|
+
<li>Event payload types must be storable and cannot be event storage types</li>
|
|
2420
|
+
<li>Event payload cannot be <code>ref[T]</code></li>
|
|
2421
|
+
<li>Event storage types cannot be returned, passed through ordinary value parameters, or copied into locals</li>
|
|
2422
|
+
<li><code>emit</code> is only callable from within the declaring module</li>
|
|
2423
|
+
<li>Events not allowed in external files</li>
|
|
2424
|
+
</ul>
|
|
2425
|
+
|
|
2426
|
+
<h3>Interface Restrictions</h3>
|
|
2427
|
+
<ul style="margin-left:1.25rem;margin-bottom:1rem">
|
|
2428
|
+
<li>Interface methods cannot be <code>async</code> or declare their own type parameters</li>
|
|
2429
|
+
<li>Interface conformance must be declared on the nominal type, not an <code>extending</code> block</li>
|
|
2422
2430
|
</ul>
|
|
2423
2431
|
</section>
|
|
2424
2432
|
|
data/docs/language-design.md
CHANGED
|
@@ -16,7 +16,7 @@ The output target is beautiful C. The generated C should be readable enough that
|
|
|
16
16
|
2. Stay statically typed. Public APIs must be explicit and unsurprising.
|
|
17
17
|
3. Feel good for game programming. Tight loops, structs, arrays, arenas, handles, and FFI should be first-class.
|
|
18
18
|
4. Interoperate with C as a native citizen. C libraries are not a side feature; they are part of the core story.
|
|
19
|
-
5. Generate C that mirrors the source closely.
|
|
19
|
+
5. Generate C that mirrors the source closely. Ordinary value operations do not add hidden heap traffic or dispatch; explicit runtime surfaces such as `dyn` and captured `proc` environments remain visible in the language model.
|
|
20
20
|
|
|
21
21
|
## Non-goals
|
|
22
22
|
|
|
@@ -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,
|
|
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.
|
|
32
32
|
|
|
33
33
|
## Design rules
|
|
34
34
|
|
|
@@ -291,7 +291,7 @@ The rule is fixed and inspectable. There is no method lookup at runtime.
|
|
|
291
291
|
### Interfaces
|
|
292
292
|
|
|
293
293
|
Interfaces are explicit nominal method-set contracts for static polymorphism.
|
|
294
|
-
They do not introduce inheritance, hidden dispatch, or a second object model.
|
|
294
|
+
They do not introduce inheritance, hidden dispatch, or a second object model. Runtime polymorphism is an explicit `dyn[Interface]` surface.
|
|
295
295
|
|
|
296
296
|
```mt
|
|
297
297
|
interface Damageable:
|
|
@@ -363,7 +363,7 @@ Because fixed arrays copy by value, mutating interface-constrained collection co
|
|
|
363
363
|
|
|
364
364
|
Iteration stays structural in v1 rather than going through a nominal `Iterator[T]` or `Iterable[T]` interface. Arrays, spans, and ranges keep their built-in behavior, and custom iterables participate by exposing the same method shape the compiler already recognizes: a non-editable `iter()` method on the iterable, then either `next() ->` nullable pointer-like item or `next() -> bool` together with `current()` on the iterator.
|
|
365
365
|
|
|
366
|
-
This is deliberate. Interfaces are
|
|
366
|
+
This is deliberate. Interfaces are nominal contracts, and generic interfaces are supported, but introducing a central iterator interface hierarchy would still either erase the item type or duplicate type-specific interfaces. The standard library should instead standardize on one iterator convention:
|
|
367
367
|
|
|
368
368
|
- `collection.iter()` is the canonical traversal surface.
|
|
369
369
|
- Alternate traversals use explicit view methods such as `map.keys()`, `map.values()`, and `map.entries()`.
|
|
@@ -484,7 +484,7 @@ The default async model is a language-integrated entry boundary. `std.async` rem
|
|
|
484
484
|
|
|
485
485
|
#### Concurrency: `parallel for` and `parallel:` blocks
|
|
486
486
|
|
|
487
|
-
Milk Tea has first-class compiler support for multithreading. The
|
|
487
|
+
Milk Tea has first-class compiler support for multithreading. The compiler-integrated `parallel for` and `parallel:` forms use structured barriers; explicit `detach` work continues until a matching `gather`. There is no hidden thread pool, no garbage collector interaction, and no implicit fire-and-forget cleanup. Every thread boundary is visible in source.
|
|
488
488
|
|
|
489
489
|
```mt
|
|
490
490
|
# Data-parallel: each iteration runs on a separate CPU core
|
|
@@ -794,7 +794,7 @@ unsafe:
|
|
|
794
794
|
Binary arithmetic and numeric comparison operators may promote primitive operands to a common type locally.
|
|
795
795
|
This is limited to `+ - * / % == != < <= > >=` and does not change assignment, return, or aggregate-field typing rules.
|
|
796
796
|
Non-external call boundaries remain strict, but external calls may pass enum or flags values to same-width fixed-width integer parameters without an explicit cast for C ABI interop.
|
|
797
|
-
Mixed signed and unsigned
|
|
797
|
+
Mixed signed and unsigned integer operands use the same lossless rule as assignment: they promote to the narrowest signed type that holds both full ranges — a strictly-wider signed type covers an unsigned operand (`ubyte + int` is `int`, `uint + long` is `long`), and equal-width or narrower signed operands widen to the next signed width (`int + uint` is `long`, `byte + ubyte` is `short`). Mixing with a 64-bit unsigned type (`ulong`, `ptr_uint`) has no safe signed common type and still requires an explicit cast.
|
|
798
798
|
|
|
799
799
|
`char` stays outside the general numeric-promotion rules. If code wants arithmetic on a character value, cast it to an integer type first. If code wants to write bytes back into a `char` buffer, either use `char<-...` explicitly or rely on the expected `char` boundary where a known `char` target is being initialized or assigned.
|
|
800
800
|
|
|
@@ -876,7 +876,7 @@ The memory model must feel like C with better defaults and cleaner surfaces.
|
|
|
876
876
|
- Fixed arrays copy by value.
|
|
877
877
|
- Returning a struct is allowed and lowers to normal C return or out-parameter lowering as needed.
|
|
878
878
|
|
|
879
|
-
|
|
879
|
+
Ordinary values do not carry hidden reference counts or heap boxes. Captured `proc` values are the explicit callable-model exception: their environments retain and release nested captured procs so stored closures have a defined lifecycle.
|
|
880
880
|
|
|
881
881
|
### Explicit allocation
|
|
882
882
|
|
|
@@ -1100,7 +1100,7 @@ Rules:
|
|
|
1100
1100
|
- one module identity per file, derived from its path
|
|
1101
1101
|
- explicit imports only
|
|
1102
1102
|
- no wildcard imports in v1
|
|
1103
|
-
-
|
|
1103
|
+
- cyclic imports are supported through forward-declaration bindings and two-pass checking
|
|
1104
1104
|
- package naming stays filesystem-friendly
|
|
1105
1105
|
|
|
1106
1106
|
Recommended layout:
|
|
@@ -1428,7 +1428,7 @@ Callbacks must map directly to C function pointers.
|
|
|
1428
1428
|
type LogCallback = fn(level: int, message: cstr, user_data: ptr[void]) -> void
|
|
1429
1429
|
```
|
|
1430
1430
|
|
|
1431
|
-
Capturing closures should not be lowered to hidden heap objects. If user state is needed, pass it explicitly as `user_data` at ABI boundaries or allocate explicit local storage such as `std.
|
|
1431
|
+
Capturing closures should not be lowered to hidden heap objects. If user state is needed, pass it explicitly as `user_data` at ABI boundaries or allocate explicit local storage such as `std.box.alloc[T](...)` in ordinary Milk Tea code.
|
|
1432
1432
|
|
|
1433
1433
|
## Data layout and ABI controls
|
|
1434
1434
|
|
data/docs/language-manual.md
CHANGED
|
@@ -292,7 +292,7 @@ Callable and `ref[...]` rules:
|
|
|
292
292
|
- External functions still cannot take `ref[...]` parameters, and ordinary functions still cannot return `ref[...]`.
|
|
293
293
|
- `proc` captures are value captures. A captured local is not a mutable alias back to the outer binding. Any storable type may be captured, including scalars, arrays, structs, and other `proc` values. Captured `proc` values participate in the ref-counted lifecycle: the capturing proc retains the captured proc on creation and releases it when the env is freed.
|
|
294
294
|
- `ref[T]` values are not capturable by design since they are non-owning.
|
|
295
|
-
- Shared mutable proc state should use explicit storage such as `std.
|
|
295
|
+
- Shared mutable proc state should use explicit storage such as `std.box.alloc[T](...)` or other explicit pointer-backed state, not implicit mutable capture.
|
|
296
296
|
|
|
297
297
|
### 3.4 Struct, union, enum, flags, opaque
|
|
298
298
|
|
|
@@ -1508,7 +1508,7 @@ The compiler intentionally rejects the following patterns. These are design cons
|
|
|
1508
1508
|
### 13.6 Type system restrictions
|
|
1509
1509
|
|
|
1510
1510
|
- conditions must be `bool`; integers and pointers have no implicit truthy or falsy coercion
|
|
1511
|
-
- mixed signed and unsigned integer arithmetic requires an explicit cast
|
|
1511
|
+
- mixed signed and unsigned integer arithmetic promotes to the narrowest signed type that holds both ranges (`ubyte + int` is `int`, `int + uint` is `long`, `byte + ubyte` is `short`); mixing with a 64-bit unsigned type (`ulong`, `ptr_uint`) still requires an explicit cast
|
|
1512
1512
|
- enum and flags values do not implicitly coerce to their backing integer types outside external-call boundaries
|
|
1513
1513
|
- `enum` backing types must be integer primitives; flags members must be compile-time integer constants
|
|
1514
1514
|
- variant arm payloads cannot use `ref[T]` in v1
|
data/lib/milk_tea/base.rb
CHANGED
|
@@ -5,7 +5,7 @@ require "open3"
|
|
|
5
5
|
require "tempfile"
|
|
6
6
|
require_relative "../tooling/formatter"
|
|
7
7
|
require_relative "../core/token"
|
|
8
|
-
require_relative "../core/types
|
|
8
|
+
require_relative "../core/types"
|
|
9
9
|
require_relative "bindgen/ast_parser"
|
|
10
10
|
require_relative "bindgen/declaration"
|
|
11
11
|
require_relative "bindgen/type_mapper"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require_relative "../tooling/formatter"
|
|
5
5
|
require_relative "../core/token"
|
|
6
|
-
require_relative "../core/types
|
|
6
|
+
require_relative "../core/types"
|
|
7
7
|
|
|
8
8
|
require_relative "imported_bindings/generator"
|
|
9
9
|
require_relative "imported_bindings/method_source"
|
|
@@ -1506,10 +1506,54 @@ module MilkTea
|
|
|
1506
1506
|
return false unless receiver_type.is_a?(Types::VariantArmPayload)
|
|
1507
1507
|
|
|
1508
1508
|
outer = receiver_type.variant_type
|
|
1509
|
-
return
|
|
1510
|
-
return field_type == outer if outer.is_a?(Types::VariantInstance)
|
|
1509
|
+
return false unless outer.is_a?(Types::Variant) || outer.is_a?(Types::VariantInstance)
|
|
1511
1510
|
|
|
1512
|
-
|
|
1511
|
+
# The C backend pointer-breaks every cyclic field, including cycles that
|
|
1512
|
+
# run through generic instantiations such as `Option[Atom]` inside `Atom`.
|
|
1513
|
+
# Deref the access whenever the field type can reach the outer variant.
|
|
1514
|
+
type_reaches_target?(field_type, outer)
|
|
1515
|
+
end
|
|
1516
|
+
|
|
1517
|
+
def type_reaches_target?(type, target)
|
|
1518
|
+
type_reaches_target_with_visited?(type, target, {})
|
|
1519
|
+
end
|
|
1520
|
+
|
|
1521
|
+
def type_reaches_target_with_visited?(type, target, visited)
|
|
1522
|
+
return true if type == target
|
|
1523
|
+
return false if visited[type.object_id]
|
|
1524
|
+
|
|
1525
|
+
visited[type.object_id] = true
|
|
1526
|
+
reachability_children(type).any? do |child|
|
|
1527
|
+
type_reaches_target_with_visited?(child, target, visited)
|
|
1528
|
+
end
|
|
1529
|
+
end
|
|
1530
|
+
|
|
1531
|
+
# Structural expansion used by cyclic-field reachability. Mirrors the
|
|
1532
|
+
# CBackend's `aggregate_type_dependencies` so the deref injected for a
|
|
1533
|
+
# pointer-broken cyclic field agrees with the emitted C struct layout.
|
|
1534
|
+
def reachability_children(type)
|
|
1535
|
+
case type
|
|
1536
|
+
when Types::Nullable
|
|
1537
|
+
[type.base]
|
|
1538
|
+
when Types::GenericInstance
|
|
1539
|
+
if pointer_type?(type)
|
|
1540
|
+
[]
|
|
1541
|
+
elsif array_type?(type)
|
|
1542
|
+
[array_element_type(type)]
|
|
1543
|
+
else
|
|
1544
|
+
[]
|
|
1545
|
+
end
|
|
1546
|
+
when Types::Span
|
|
1547
|
+
[type.element_type]
|
|
1548
|
+
when Types::Struct, Types::StructInstance, Types::Union
|
|
1549
|
+
type.fields.values
|
|
1550
|
+
when Types::Variant, Types::VariantInstance
|
|
1551
|
+
type.arm_names.flat_map { |name| type.arm(name).values }
|
|
1552
|
+
when Types::VariantArmPayload
|
|
1553
|
+
type.fields.values
|
|
1554
|
+
else
|
|
1555
|
+
[]
|
|
1556
|
+
end
|
|
1513
1557
|
end
|
|
1514
1558
|
|
|
1515
1559
|
def lower_compile_time_handle_member(handle, member, type)
|
|
@@ -1137,9 +1137,33 @@ module MilkTea
|
|
|
1137
1137
|
return unless left_type.is_a?(Types::Primitive) && right_type.is_a?(Types::Primitive)
|
|
1138
1138
|
return unless left_type.integer? && right_type.integer?
|
|
1139
1139
|
return unless left_type.fixed_width_integer? && right_type.fixed_width_integer?
|
|
1140
|
-
return unless left_type.signed_integer? == right_type.signed_integer?
|
|
1141
1140
|
|
|
1142
|
-
|
|
1141
|
+
# Mirrors the semantic analyzer's rule: same signedness picks the wider
|
|
1142
|
+
# type; mixed signed/unsigned promotes to the narrowest signed type that
|
|
1143
|
+
# holds both operands' full ranges (a strictly-wider signed type covers
|
|
1144
|
+
# an unsigned operand, otherwise widen to the next signed width). Mixing
|
|
1145
|
+
# with a 64-bit unsigned type has no safe signed common type.
|
|
1146
|
+
if left_type.signed_integer? == right_type.signed_integer?
|
|
1147
|
+
return left_type.integer_width >= right_type.integer_width ? left_type : right_type
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
signed_type, unsigned_type = if left_type.signed_integer?
|
|
1151
|
+
[left_type, right_type]
|
|
1152
|
+
else
|
|
1153
|
+
[right_type, left_type]
|
|
1154
|
+
end
|
|
1155
|
+
|
|
1156
|
+
return signed_type if signed_type.integer_width > unsigned_type.integer_width
|
|
1157
|
+
|
|
1158
|
+
signed_type_above_width(unsigned_type.integer_width)
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1161
|
+
def signed_type_above_width(width)
|
|
1162
|
+
case width
|
|
1163
|
+
when 8 then @ctx.types.fetch("short")
|
|
1164
|
+
when 16 then @ctx.types.fetch("int")
|
|
1165
|
+
when 32 then @ctx.types.fetch("long")
|
|
1166
|
+
end
|
|
1143
1167
|
end
|
|
1144
1168
|
|
|
1145
1169
|
def wider_float_type(left_type, right_type)
|
|
@@ -943,7 +943,12 @@ module MilkTea
|
|
|
943
943
|
payload_type = Types::VariantArmPayload.new(storage_type, arm_name, storage_type.arm(arm_name))
|
|
944
944
|
data_expr = IR::Member.new(receiver: storage_expr, member: "data", type: nil)
|
|
945
945
|
arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
|
|
946
|
-
IR::Member.new(receiver: arm_expr, member: field_name, type: field_type)
|
|
946
|
+
member_expr = IR::Member.new(receiver: arm_expr, member: field_name, type: field_type)
|
|
947
|
+
if type_reaches_target?(field_type, payload_type.variant_type)
|
|
948
|
+
return IR::Unary.new(operator: "*", operand: member_expr, type: field_type)
|
|
949
|
+
end
|
|
950
|
+
|
|
951
|
+
member_expr
|
|
947
952
|
end
|
|
948
953
|
|
|
949
954
|
def infer_result_propagation_type(expression, env:)
|
|
@@ -187,9 +187,36 @@ module MilkTea
|
|
|
187
187
|
return unless left_type.integer? && right_type.integer?
|
|
188
188
|
return left_type if left_type == right_type
|
|
189
189
|
return unless left_type.fixed_width_integer? && right_type.fixed_width_integer?
|
|
190
|
-
return unless left_type.signed_integer? == right_type.signed_integer?
|
|
191
190
|
|
|
192
|
-
|
|
191
|
+
# Same signedness: the wider type wins (both operands are losslessly
|
|
192
|
+
# assignable to it).
|
|
193
|
+
if left_type.signed_integer? == right_type.signed_integer?
|
|
194
|
+
return left_type.integer_width >= right_type.integer_width ? left_type : right_type
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Mixed signed/unsigned: promote to the narrowest signed type that holds
|
|
198
|
+
# both operands' full ranges, mirroring the lossless assignment rule. A
|
|
199
|
+
# strictly-wider signed type covers an unsigned operand; equal-width or
|
|
200
|
+
# wider unsigned operands widen to the next signed width. Mixing with a
|
|
201
|
+
# 64-bit unsigned type has no safe signed common type, so callers fall
|
|
202
|
+
# back to requiring an explicit cast.
|
|
203
|
+
signed_type, unsigned_type = if left_type.signed_integer?
|
|
204
|
+
[left_type, right_type]
|
|
205
|
+
else
|
|
206
|
+
[right_type, left_type]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
return signed_type if signed_type.integer_width > unsigned_type.integer_width
|
|
210
|
+
|
|
211
|
+
signed_type_above_width(unsigned_type.integer_width)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def signed_type_above_width(width)
|
|
215
|
+
case width
|
|
216
|
+
when 8 then @ctx.types.fetch("short")
|
|
217
|
+
when 16 then @ctx.types.fetch("int")
|
|
218
|
+
when 32 then @ctx.types.fetch("long")
|
|
219
|
+
end
|
|
193
220
|
end
|
|
194
221
|
|
|
195
222
|
def wider_float_type(left_type, right_type)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fiddle"
|
|
4
|
-
require_relative "registry"
|
|
5
|
-
require_relative "visitor"
|
|
4
|
+
require_relative "types/registry"
|
|
5
|
+
require_relative "types/visitor"
|
|
6
6
|
|
|
7
7
|
module MilkTea
|
|
8
8
|
module Types
|
|
@@ -850,9 +850,6 @@ module MilkTea
|
|
|
850
850
|
end
|
|
851
851
|
|
|
852
852
|
def field_c_name(name)
|
|
853
|
-
stripped = name.delete_suffix("_")
|
|
854
|
-
return stripped if stripped != name && MilkTea::KEYWORDS.key?(stripped)
|
|
855
|
-
|
|
856
853
|
name
|
|
857
854
|
end
|
|
858
855
|
|
|
@@ -961,9 +958,6 @@ module MilkTea
|
|
|
961
958
|
end
|
|
962
959
|
|
|
963
960
|
def field_c_name(name)
|
|
964
|
-
stripped = name.delete_suffix("_")
|
|
965
|
-
return stripped if stripped != name && MilkTea::KEYWORDS.key?(stripped)
|
|
966
|
-
|
|
967
961
|
name
|
|
968
962
|
end
|
|
969
963
|
|
data/lib/milk_tea/core.rb
CHANGED
|
@@ -9,7 +9,7 @@ require_relative "core/lexer"
|
|
|
9
9
|
require_relative "core/cst"
|
|
10
10
|
require_relative "core/cst_builder"
|
|
11
11
|
require_relative "core/ast"
|
|
12
|
-
require_relative "core/types
|
|
12
|
+
require_relative "core/types"
|
|
13
13
|
require_relative "core/binding_types"
|
|
14
14
|
require_relative "core/compile_time"
|
|
15
15
|
require_relative "core/compatibility_helpers"
|
data/lib/milk_tea/tooling/cli.rb
CHANGED
|
@@ -229,6 +229,7 @@ module MilkTea
|
|
|
229
229
|
|
|
230
230
|
def lex_command
|
|
231
231
|
path = nil
|
|
232
|
+
sexpr = false
|
|
232
233
|
|
|
233
234
|
args = @argv.dup
|
|
234
235
|
@argv = []
|
|
@@ -236,6 +237,11 @@ module MilkTea
|
|
|
236
237
|
arg = args.shift
|
|
237
238
|
next if arg == "--"
|
|
238
239
|
|
|
240
|
+
if arg == "--sexpr"
|
|
241
|
+
sexpr = true
|
|
242
|
+
next
|
|
243
|
+
end
|
|
244
|
+
|
|
239
245
|
if path.nil?
|
|
240
246
|
path = arg
|
|
241
247
|
else
|
|
@@ -252,15 +258,24 @@ module MilkTea
|
|
|
252
258
|
end
|
|
253
259
|
|
|
254
260
|
tokens = Lexer.lex(read_source_file(path), path: path)
|
|
255
|
-
|
|
261
|
+
if sexpr
|
|
262
|
+
@out.puts(SexprDumper.dump_tokens(tokens))
|
|
263
|
+
else
|
|
264
|
+
@out.write(PP.pp(tokens, +""))
|
|
265
|
+
end
|
|
256
266
|
0
|
|
257
267
|
end
|
|
258
268
|
|
|
259
269
|
def parse_command
|
|
270
|
+
sexpr = false
|
|
260
271
|
args = @argv.dup
|
|
261
272
|
@argv = []
|
|
262
273
|
until args.empty?
|
|
263
274
|
arg = args.shift
|
|
275
|
+
if arg == "--sexpr"
|
|
276
|
+
sexpr = true
|
|
277
|
+
next
|
|
278
|
+
end
|
|
264
279
|
@argv << arg
|
|
265
280
|
end
|
|
266
281
|
|
|
@@ -283,9 +298,13 @@ module MilkTea
|
|
|
283
298
|
paths.each_with_index do |path, index|
|
|
284
299
|
ast = make_module_loader(path, locked: resolution[:locked], platform: ModuleLoader.default_host_platform).load_file(path)
|
|
285
300
|
if multiple
|
|
286
|
-
@out.puts("# --- #{path} ---")
|
|
301
|
+
@out.puts("# --- #{path} ---") unless sexpr
|
|
302
|
+
end
|
|
303
|
+
if sexpr
|
|
304
|
+
@out.puts(SexprDumper.dump_ast(ast))
|
|
305
|
+
else
|
|
306
|
+
@out.write(PrettyPrinter.format_ast(ast))
|
|
287
307
|
end
|
|
288
|
-
@out.write(PrettyPrinter.format_ast(ast))
|
|
289
308
|
@out.puts if multiple && index < paths.length - 1
|
|
290
309
|
end
|
|
291
310
|
0
|
|
@@ -764,10 +783,15 @@ module MilkTea
|
|
|
764
783
|
end
|
|
765
784
|
|
|
766
785
|
def lower_command
|
|
786
|
+
sexpr = false
|
|
767
787
|
args = @argv.dup
|
|
768
788
|
@argv = []
|
|
769
789
|
until args.empty?
|
|
770
790
|
arg = args.shift
|
|
791
|
+
if arg == "--sexpr"
|
|
792
|
+
sexpr = true
|
|
793
|
+
next
|
|
794
|
+
end
|
|
771
795
|
@argv << arg
|
|
772
796
|
end
|
|
773
797
|
|
|
@@ -790,10 +814,13 @@ module MilkTea
|
|
|
790
814
|
paths.each_with_index do |path, index|
|
|
791
815
|
program = make_module_loader(path, locked: resolution[:locked], platform: ModuleLoader.default_host_platform).check_program(path)
|
|
792
816
|
if multiple
|
|
793
|
-
@out.puts("# --- #{path} ---")
|
|
817
|
+
@out.puts("# --- #{path} ---") unless sexpr
|
|
818
|
+
end
|
|
819
|
+
if sexpr
|
|
820
|
+
@out.puts(SexprDumper.dump_ir(Lowering.lower(program)))
|
|
821
|
+
else
|
|
822
|
+
@out.write(PrettyPrinter.format_ir(Lowering.lower(program)))
|
|
794
823
|
end
|
|
795
|
-
@out.write(PrettyPrinter.format_ir(Lowering.lower(program)))
|
|
796
|
-
@out.puts if multiple && index < paths.length - 1
|
|
797
824
|
end
|
|
798
825
|
0
|
|
799
826
|
end
|