mt-lang 0.4.25 → 0.4.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +39 -7
- data/docs/build-guide.md +5 -0
- data/docs/index.html +61 -29
- data/docs/language-design.md +2 -2
- data/docs/language-manual.md +15 -3
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/bindings/raw_bindings/defaults.rb +22 -0
- data/lib/milk_tea/core/c_backend/statements.rb +118 -1
- data/lib/milk_tea/core/lowering/utils.rb +8 -1
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +43 -2
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +7 -1
- data/lib/milk_tea/tooling/bindgen_cli.rb +56 -0
- data/lib/milk_tea/tooling/cli.rb +2 -1
- data/std/c/stb_image.mt +15 -15
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f75e56a0679c161c076efb3dd694afe256154c512e7ffae171e13c2d98d181d
|
|
4
|
+
data.tar.gz: ff00b0459c88d4fd31d12b0fc25e953d339fe7d8f7ed10964b5d8e2b70065a6f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 72e4cfb94f691ace787f420cbd2b6481c0b189282d21dbe42b47ebd795844f5e42a59a95fc64f1e75f56c6ec7119723231fef4601c8d856f6d7ada5ed107d26f
|
|
7
|
+
data.tar.gz: 01ba76cc9a0aeb3ab65fbd5dea21a9995ace49a3328003352fa13b0c54c9cc42e8b3e1bb6423d703913439ca4de91b4f70348d128a6c813a835c28e9345ff88b
|
data/README.md
CHANGED
|
@@ -840,6 +840,8 @@ Nullability:
|
|
|
840
840
|
- For non-pointer value bases (`int`, `bool`, `float`, structs, ...), `T?` is stored inline by value as a tagged optional (a presence flag plus the value). It copies by value with no hidden heap allocation or pointer aliasing.
|
|
841
841
|
- Use `null` for absence in any nullable context.
|
|
842
842
|
- In nullable pointer-like contexts, prefer `null` over `zero[ptr[T]]`.
|
|
843
|
+
- `null` is only a value of nullable types. Comparing a non-nullable pointer against `null` is a type error: either declare the value `T?` when null is possible, or compare against `zero[ptr[T]]` (also `zero[cstr]`, `zero[own[T]]`) when testing the raw C zero pointer at the raw ABI level. Nullable types must use `null`, not `zero[...]`.
|
|
844
|
+
- Null checks narrow flow: inside the `!= null` branch (and after an `== null` branch exits), the binding is treated as non-null. A further null check on a narrowed binding is still legal; the linter reports it as `redundant-null-check`.
|
|
843
845
|
- `ref[T]` is non-null and cannot be nullable.
|
|
844
846
|
- At an FFI boundary (`external` / `foreign function` parameters and returns), only pointer-like `T?` is allowed. A non-pointer value nullable such as `int?` is rejected — use `ptr[T]?` or pass an explicit struct.
|
|
845
847
|
|
|
@@ -911,6 +913,8 @@ A `field_handle`'s `.type` is usable directly **in type position** within a comp
|
|
|
911
913
|
|
|
912
914
|
### Standard library
|
|
913
915
|
|
|
916
|
+
The authoritative module catalog is `mtc std list [--json]`; it discovers every hand-written module shipped in `std/` with its category, description, and path. Generated binding modules (`std/c/*` and imported-binding wrappers such as `raylib` or `zstd`) are excluded from `list` but stay viewable with `mtc std show`.
|
|
917
|
+
|
|
914
918
|
Core modules in `std/`:
|
|
915
919
|
|
|
916
920
|
- `std.linear_algebra` — extends native vector/matrix/quaternion types with `dot`, `cross`, `length`, `normalized`, `lerp`, `identity`, `transpose`, `conjugate` (pure Mt, no C dependency beyond `std.math` for `sqrt`)
|
|
@@ -922,7 +926,7 @@ Core modules in `std/`:
|
|
|
922
926
|
- `std.encoding` — UTF-8 validation (`is_valid_utf8`, `utf8_codepoint_count`, `decode_utf8_codepoint`, `utf8_overlong_check`)
|
|
923
927
|
- `std.string.String` — growable owned UTF-8 text
|
|
924
928
|
- `std.mem.heap`, `std.mem.arena`, `std.mem.pool`, `std.mem.stack`, `std.mem.tracking`, `std.mem.endian` — allocators and memory utilities
|
|
925
|
-
- `std.async` — task runtime (`sleep`, `work`, `completed`, `result`, `wait`, `run`)
|
|
929
|
+
- `std.async` — task runtime (`sleep`, `work`, `completed`, `result`, `wait`, `run`, plus explicit-runtime variants `wait_on`, `work_on`, `sleep_on`, `run_on`, `pump`, `with_runtime`)
|
|
926
930
|
- `std.option.Option[T]` — optional value with `is_some`, `is_none`, `unwrap`, `expect`, `unwrap_or`, `unwrap_or_else` (auto-imported via prelude)
|
|
927
931
|
- `std.result.Result[T, E]` — fallible computation with `is_success`, `is_failure`, `unwrap`, `unwrap_error`, `unwrap_or`, `unwrap_or_else`, `ok`, `error`, `map_error` (auto-imported via prelude)
|
|
928
932
|
|
|
@@ -1241,6 +1245,13 @@ Resolve a module by name, build it, and run it:
|
|
|
1241
1245
|
mtc run-module <module> # Resolve, build, and run a module by name (e.g. std.http.server)
|
|
1242
1246
|
```
|
|
1243
1247
|
|
|
1248
|
+
Standard library inspection:
|
|
1249
|
+
|
|
1250
|
+
```
|
|
1251
|
+
mtc std list [--json] # List hand-written std modules grouped by category
|
|
1252
|
+
mtc std show MODULE # Print a module's source (dotted or slashed names)
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1244
1255
|
Toolchain maintenance:
|
|
1245
1256
|
|
|
1246
1257
|
```
|
|
@@ -1259,6 +1270,28 @@ mtc dap # Start the Debug Adapter Protocol server
|
|
|
1259
1270
|
mtc docs [--open] [--port P] # Serve the local documentation site
|
|
1260
1271
|
```
|
|
1261
1272
|
|
|
1273
|
+
`mtc bindgen` nullable policy:
|
|
1274
|
+
|
|
1275
|
+
C headers usually do not state which functions can return NULL. A nullable policy file lists those symbols so generated raw bindings expose them as `T?` and null checks work downstream. The policy feeds the same override machinery as the checked-in `std.c.*` registries and shows up in `--nullable-report`.
|
|
1276
|
+
|
|
1277
|
+
```sh
|
|
1278
|
+
mtc bindgen mylib.mt mylib.h --nullable-policy nullable_policy.json --nullable-report report.json
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
```json
|
|
1282
|
+
{
|
|
1283
|
+
"return_types": {
|
|
1284
|
+
"mylib_load": "ptr[ubyte]?",
|
|
1285
|
+
"mylib_get_error": "cstr?"
|
|
1286
|
+
},
|
|
1287
|
+
"parameters": {
|
|
1288
|
+
"mylib_read": { "out_size": "ptr[uint]?" }
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
Types must match what bindgen would otherwise emit for the symbol (minus the `?`). Bindgen rejects policy entries for unknown symbols, so a stale policy fails loudly instead of silently drifting.
|
|
1294
|
+
|
|
1262
1295
|
Build and run commands support `--profile`, `--platform`, `--cc`, `--keep-c`, `--locked`, `--frozen`, and `-I` include paths. Dependency-locked flows support `--locked` (use package.lock) and `--frozen` (require current package.lock).
|
|
1263
1296
|
|
|
1264
1297
|
Global options work with any command (before or after the subcommand, up to a `--` separator): `-h`/`--help` (also `mtc help <command>` for command-specific help), `-V`/`--version`, `-q`/`--quiet` (suppress informational output), `-v`/`--verbose` (per-file progress), and `--color auto|always|never`. Generate shell completions with `mtc completions bash|zsh|fish`. Timing breakdowns use `--timings` (on `format` and `lint`); `lint` reports warnings with per-file progress.
|
|
@@ -1266,14 +1299,13 @@ Global options work with any command (before or after the subcommand, up to a `-
|
|
|
1266
1299
|
Diagnostic output uses standard compiler format (file:line:column with source context, error codes, and caret highlighting):
|
|
1267
1300
|
|
|
1268
1301
|
```
|
|
1269
|
-
[
|
|
1270
|
-
--> file.mt:1:16
|
|
1302
|
+
error[sema/error]: unknown type floa -- /tmp/diag_probe.mt:2:12
|
|
1271
1303
|
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1304
|
+
2 | let x: floa = 1
|
|
1305
|
+
| ^~~~
|
|
1306
|
+
= help: did you mean 'float'?
|
|
1275
1307
|
|
|
1276
|
-
|
|
1308
|
+
1 error found
|
|
1277
1309
|
```
|
|
1278
1310
|
|
|
1279
1311
|
`mtc check` surfaces both errors and linter warnings:
|
data/docs/build-guide.md
CHANGED
|
@@ -351,6 +351,11 @@ Common options:
|
|
|
351
351
|
- `--bundle` for native package builds when you want a distributable app directory instead of a bare executable
|
|
352
352
|
- `--archive` for native package builds when you also want a `.tar.gz` archive of the bundle; this implies `--bundle`
|
|
353
353
|
|
|
354
|
+
Debug profile builds enable runtime debug guards:
|
|
355
|
+
|
|
356
|
+
- Loops get an iteration-limit guard that calls `mt_fatal` when a synchronous loop exceeds 50,000,000 iterations (a hang detector).
|
|
357
|
+
- Every unconditionally-evaluated raw pointer dereference — pointer field access, pointer indexing, and `read(ptr)` in `unsafe` — is preceded by a null trap that calls `mt_fatal("null pointer dereferenced in <function>")` instead of segfaulting. This catches a NULL that crossed a non-nullable FFI declaration at the source line. Derefs guarded only by flow (inside `and`/`or` right sides or `?:` branches) and `ref[T]` receivers are not trapped: conditional derefs may never execute, and refs are non-null by the type system.
|
|
358
|
+
|
|
354
359
|
The wasm platform also accepts the aliases `web`, `html5`, and `browser`.
|
|
355
360
|
|
|
356
361
|
For editor tooling, the Milk Tea VS Code extension also accepts `milkTea.lsp.platform = auto|linux|windows|wasm`.
|
data/docs/index.html
CHANGED
|
@@ -254,7 +254,7 @@ p + p{margin-top:-.25rem}
|
|
|
254
254
|
</button>
|
|
255
255
|
<span style="font-size:.75rem">
|
|
256
256
|
<a href="https://github.com/teefan/mt-lang" style="color:inherit;text-decoration:none" title="Source code on GitHub">☝ GitHub</a>
|
|
257
|
-
·
|
|
257
|
+
· v0.4
|
|
258
258
|
</span>
|
|
259
259
|
</div>
|
|
260
260
|
</aside>
|
|
@@ -2101,7 +2101,7 @@ async function main() -> int:
|
|
|
2101
2101
|
</div>
|
|
2102
2102
|
|
|
2103
2103
|
<h3>Task Helpers</h3>
|
|
2104
|
-
<p>Import <code>std.async</code> for runtime control:</p>
|
|
2104
|
+
<p>Import <code>std.async</code> for runtime control (<code>sleep</code>, <code>work</code>, <code>completed</code>, <code>result</code>, <code>wait</code>, <code>run</code>, plus explicit-runtime variants such as <code>wait_on</code>, <code>work_on</code>, and <code>with_runtime</code>):</p>
|
|
2105
2105
|
<div class="code-wrap">
|
|
2106
2106
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
2107
2107
|
<pre><code>import std.async as aio
|
|
@@ -2306,7 +2306,7 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
|
|
|
2306
2306
|
<tr><td><code>std.mem.arena</code></td><td>Frame/level/scratch arena allocator</td></tr>
|
|
2307
2307
|
<tr><td><code>std.mem.pool</code></td><td>Fixed-size object pool</td></tr>
|
|
2308
2308
|
<tr><td><code>std.mem.stack</code></td><td>Explicit temporary allocator</td></tr>
|
|
2309
|
-
<tr><td><code>std.async</code></td><td>Async runtime: <code>sleep</code>, <code>work</code>, <code>completed</code>, <code>result</code>, <code>wait</code>, <code>run</code
|
|
2309
|
+
<tr><td><code>std.async</code></td><td>Async runtime: <code>sleep</code>, <code>work</code>, <code>completed</code>, <code>result</code>, <code>wait</code>, <code>run</code>, plus explicit-runtime variants (<code>wait_on</code>, <code>work_on</code>, <code>with_runtime</code>)</td></tr>
|
|
2310
2310
|
<tr><td><code>std.option</code></td><td><code>Option[T]</code> — optional value (prelude type; methods always available without import). Methods: <code>is_some</code>, <code>is_none</code>, <code>unwrap</code>, <code>expect</code>, <code>unwrap_or</code>, <code>unwrap_or_else</code></td></tr>
|
|
2311
2311
|
<tr><td><code>std.result</code></td><td><code>Result[T, E]</code> — fallible computation (prelude type; methods always available without import). Methods: <code>is_success</code>, <code>is_failure</code>, <code>unwrap</code>, <code>unwrap_error</code>, <code>unwrap_or</code>, <code>unwrap_or_else</code>, <code>ok</code>, <code>error</code>, <code>map_error</code></td></tr>
|
|
2312
2312
|
</table>
|
|
@@ -2356,6 +2356,8 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
|
|
|
2356
2356
|
<tr><td><code>mtc test <path></code></td><td>Discover and run <code>@[test]</code> functions</td></tr>
|
|
2357
2357
|
<tr><td><code>mtc new <name></code></td><td>Scaffold a new package (<code>package.toml</code> + <code>src/main.mt</code>)</td></tr>
|
|
2358
2358
|
<tr><td><code>mtc run-module <module></code></td><td>Resolve a module by name, build it, and run it</td></tr>
|
|
2359
|
+
<tr><td><code>mtc std list [--json]</code></td><td>List hand-written standard library modules grouped by category (<code>--json</code> for machine-readable output)</td></tr>
|
|
2360
|
+
<tr><td><code>mtc std show <module></code></td><td>Print a standard library module's source (dotted or slashed names; platform variants resolve to the active platform)</td></tr>
|
|
2359
2361
|
<tr><td><code>mtc completions <shell></code></td><td>Print a bash/zsh/fish completion script</td></tr>
|
|
2360
2362
|
</table>
|
|
2361
2363
|
</div>
|
|
@@ -2430,46 +2432,76 @@ mtc lint path/to/file.mt --ignore line-too-long # skip selected rules</code></p
|
|
|
2430
2432
|
</div>
|
|
2431
2433
|
|
|
2432
2434
|
<h3>Inline Suppression</h3>
|
|
2433
|
-
<p>Suppress a
|
|
2435
|
+
<p>Suppress all rules on a line with <code># lint: ignore</code>, or specific rules with <code># lint: ignore(rule1, rule2)</code>:</p>
|
|
2434
2436
|
<div class="code-wrap">
|
|
2435
2437
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
2436
|
-
<pre><code
|
|
2437
|
-
|
|
2438
|
+
<pre><code>var count = 0 # lint: ignore
|
|
2439
|
+
var total = 0 # lint: ignore(prefer-let, dead-assignment)</code></pre>
|
|
2438
2440
|
</div>
|
|
2439
2441
|
|
|
2440
2442
|
<h3>Configuration</h3>
|
|
2441
|
-
<p>
|
|
2443
|
+
<p>Create a default config with <code>mtc lint --init</code>, or place a <code>.mt-lint.yml</code> file in the project root (or any ancestor directory):</p>
|
|
2442
2444
|
<div class="code-wrap">
|
|
2443
2445
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
2444
|
-
<pre><code>
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2446
|
+
<pre><code>max_line_length: 120
|
|
2447
|
+
select:
|
|
2448
|
+
- line-too-long
|
|
2449
|
+
- prefer-let
|
|
2450
|
+
- missing-return
|
|
2451
|
+
ignore:
|
|
2452
|
+
- shadow
|
|
2453
|
+
- useless-expression</code></pre>
|
|
2448
2454
|
</div>
|
|
2455
|
+
<p><code>max_line_length</code> defaults to <code>120</code> when omitted. When both <code>select</code> and <code>ignore</code> are present, <code>select</code> takes precedence.</p>
|
|
2449
2456
|
|
|
2450
2457
|
<h3>Rule Reference</h3>
|
|
2451
2458
|
<div class="table-wrap">
|
|
2452
2459
|
<table class="attr-table">
|
|
2453
|
-
<tr><th>
|
|
2454
|
-
<tr><td
|
|
2455
|
-
<tr><td
|
|
2456
|
-
<tr><td
|
|
2457
|
-
<tr><td
|
|
2458
|
-
<tr><td
|
|
2459
|
-
<tr><td
|
|
2460
|
-
<tr><td
|
|
2461
|
-
<tr><td
|
|
2462
|
-
<tr><td
|
|
2463
|
-
<tr><td
|
|
2464
|
-
<tr><td
|
|
2465
|
-
<tr><td
|
|
2466
|
-
<tr><td
|
|
2467
|
-
<tr><td
|
|
2468
|
-
<tr><td>
|
|
2469
|
-
<tr><td>
|
|
2460
|
+
<tr><th>Code</th><th>Severity</th><th>Auto-fix</th><th>Description</th></tr>
|
|
2461
|
+
<tr><td><code>borrow-and-mutate</code></td><td>warning</td><td>—</td><td>Local is borrowed with <code>ref_of</code>/<code>ptr_of</code> and also mutated in the same scope</td></tr>
|
|
2462
|
+
<tr><td><code>constant-condition</code></td><td>warning</td><td>—</td><td>Branch or loop condition is provably always true or false</td></tr>
|
|
2463
|
+
<tr><td><code>dead-assignment</code></td><td>warning</td><td>—</td><td>Assigned value is overwritten before any read</td></tr>
|
|
2464
|
+
<tr><td><code>duplicate-if-condition</code></td><td>warning</td><td>—</td><td><code>if</code>/<code>else if</code> branch repeats a previous condition and is unreachable</td></tr>
|
|
2465
|
+
<tr><td><code>directional-ffi-arg</code></td><td>hint</td><td>—</td><td>Legacy <code>ptr_of</code>/<code>ref_of</code>/<code>out</code> call-site wrapper is redundant for directional FFI parameters</td></tr>
|
|
2466
|
+
<tr><td><code>doc-tag</code></td><td>hint</td><td>—</td><td><code>##</code> doc comment tag (<code>@param</code>, <code>@return</code>, <code>@throws</code>, <code>@see</code>) is invalid or inconsistent</td></tr>
|
|
2467
|
+
<tr><td><code>event-capacity</code></td><td>warning</td><td>—</td><td>Event capacity may copy too many listeners to stack on emit</td></tr>
|
|
2468
|
+
<tr><td><code>line-too-long</code></td><td>warning</td><td>—</td><td>Source line exceeds configured maximum length</td></tr>
|
|
2469
|
+
<tr><td><code>loop-single-iteration</code></td><td>warning</td><td>—</td><td>Loop body always exits on the first iteration</td></tr>
|
|
2470
|
+
<tr><td><code>missing-return</code></td><td>error</td><td>—</td><td>Non-void function lacks a guaranteed return on all paths</td></tr>
|
|
2471
|
+
<tr><td><code>noop-compound-assignment</code></td><td>hint</td><td>—</td><td>Compound assignment uses an identity value and has no effect</td></tr>
|
|
2472
|
+
<tr><td><code>owning-release-double</code></td><td>warning</td><td>—</td><td>Owning binding may be released more than once</td></tr>
|
|
2473
|
+
<tr><td><code>owning-release-leak</code></td><td>warning</td><td>—</td><td>Owning binding is never released</td></tr>
|
|
2474
|
+
<tr><td><code>platform-api-drift</code></td><td>warning</td><td>—</td><td>Public API differs across platform-specific variants of the same module</td></tr>
|
|
2475
|
+
<tr><td><code>prefer-let</code></td><td>hint</td><td>yes</td><td><code>var</code> binding is never mutated; use <code>let</code></td></tr>
|
|
2476
|
+
<tr><td><code>prefer-let-else</code></td><td>hint</td><td>yes</td><td>Nullable guard can be rewritten as <code>let ... else:</code></td></tr>
|
|
2477
|
+
<tr><td><code>prefer-inline-methods</code></td><td>hint</td><td>yes</td><td>Methods on a struct can be written inline inside the struct declaration</td></tr>
|
|
2478
|
+
<tr><td><code>prefer-is-variant</code></td><td>hint</td><td>—</td><td>A match that maps one variant arm to a boolean can be <code>expr is Arm</code></td></tr>
|
|
2479
|
+
<tr><td><code>prefer-own-ptr</code></td><td>hint</td><td>—</td><td>Pointer binding used only inside <code>unsafe</code> could be <code>own[T]</code> for auto-deref</td></tr>
|
|
2480
|
+
<tr><td><code>prefer-or-pattern</code></td><td>hint</td><td>—</td><td>Adjacent match arms with identical bodies can merge with <code>|</code></td></tr>
|
|
2481
|
+
<tr><td><code>prefer-struct-with</code></td><td>hint</td><td>—</td><td>Field copies from another value can use <code>.with(...)</code></td></tr>
|
|
2482
|
+
<tr><td><code>prefer-try</code></td><td>hint</td><td>—</td><td>A match that only propagates the failure branch can use <code>expr?</code></td></tr>
|
|
2483
|
+
<tr><td><code>prefer-var-else</code></td><td>hint</td><td>yes</td><td>Nullable guard can be rewritten as <code>var ... else:</code></td></tr>
|
|
2484
|
+
<tr><td><code>redundant-bool-compare</code></td><td>hint</td><td>yes</td><td>Comparing a boolean expression to <code>true</code>/<code>false</code> is redundant</td></tr>
|
|
2485
|
+
<tr><td><code>redundant-cast</code></td><td>hint</td><td>yes</td><td>Explicit cast where implicit widening already works</td></tr>
|
|
2486
|
+
<tr><td><code>redundant-else</code></td><td>warning</td><td>yes</td><td><code>else</code> block is unnecessary because all prior branches return</td></tr>
|
|
2487
|
+
<tr><td><code>redundant-ignored-match-binding</code></td><td>hint</td><td>yes</td><td>Ignored <code>as _</code> match binding is redundant</td></tr>
|
|
2488
|
+
<tr><td><code>redundant-null-check</code></td><td>hint</td><td>—</td><td>Null check on a value already known non-null by flow analysis</td></tr>
|
|
2489
|
+
<tr><td><code>redundant-return</code></td><td>hint</td><td>yes</td><td>Final bare <code>return</code> in a void function is unnecessary</td></tr>
|
|
2490
|
+
<tr><td><code>redundant-type-annotation</code></td><td>hint</td><td>yes</td><td>Type annotation is redundant; the type is inferred from the initializer</td></tr>
|
|
2491
|
+
<tr><td><code>redundant-unsafe</code></td><td>hint</td><td>yes</td><td><code>unsafe</code> block contains no unsafe operations and can be removed</td></tr>
|
|
2492
|
+
<tr><td><code>reserved-primitive-name</code></td><td>warning</td><td>yes</td><td>Binding uses a reserved built-in type name in its active namespace</td></tr>
|
|
2493
|
+
<tr><td><code>self-assignment</code></td><td>warning</td><td>—</td><td>Variable is assigned to itself</td></tr>
|
|
2494
|
+
<tr><td><code>self-comparison</code></td><td>warning</td><td>—</td><td>Value is compared to itself, making the condition constant</td></tr>
|
|
2495
|
+
<tr><td><code>shadow</code></td><td>warning</td><td>—</td><td>Local binding shadows an outer binding with the same name</td></tr>
|
|
2496
|
+
<tr><td><code>trailing-list-comma</code></td><td>hint</td><td>yes</td><td>Trailing comma in call argument list is redundant</td></tr>
|
|
2497
|
+
<tr><td><code>unreachable-code</code></td><td>warning</td><td>—</td><td>Code after a guaranteed terminator cannot execute</td></tr>
|
|
2498
|
+
<tr><td><code>unused-import</code></td><td>warning</td><td>—</td><td>Import alias is never referenced (intentionally not auto-fixable: removals can drop extension methods or canonical hooks)</td></tr>
|
|
2499
|
+
<tr><td><code>unused-local</code></td><td>warning</td><td>—</td><td>Local binding is never referenced</td></tr>
|
|
2500
|
+
<tr><td><code>unused-param</code></td><td>warning</td><td>—</td><td>Parameter is never referenced</td></tr>
|
|
2501
|
+
<tr><td><code>useless-expression</code></td><td>warning</td><td>—</td><td>Expression statement has no side effects and its result is unused</td></tr>
|
|
2470
2502
|
</table>
|
|
2471
2503
|
</div>
|
|
2472
|
-
<p>Severity levels: <code>error</code> (fail build), <code>
|
|
2504
|
+
<p>Severity levels: <code>error</code> (fail build), <code>warning</code>, and <code>hint</code>.</p>
|
|
2473
2505
|
</section>
|
|
2474
2506
|
|
|
2475
2507
|
<!-- ────────────────────────────────────────────────────────────────────────── -->
|
data/docs/language-design.md
CHANGED
|
@@ -473,12 +473,12 @@ Current implemented shape:
|
|
|
473
473
|
|
|
474
474
|
- `async function` lifts its return type to `Task[T]`
|
|
475
475
|
- `await` is only valid inside async functions
|
|
476
|
-
- async entrypoint bootstrapping is compiler-owned, but async helpers stay explicit library surface; import `std.async as aio` when user code needs `sleep`, `work`, `completed`, `result`, `wait`, `run`, or runtime control
|
|
476
|
+
- async entrypoint bootstrapping is compiler-owned, but async helpers stay explicit library surface; import `std.async as aio` when user code needs `sleep`, `work`, `completed`, `result`, `wait`, `run`, `wait_on`, `with_runtime`, or runtime control
|
|
477
477
|
- `aio.wait(...)` and `aio.run(...)` accept direct task expressions as well as zero-arg task roots; the compiler rewrites the direct-task form into the deferred root shape automatically
|
|
478
478
|
- async bodies support ordinary local declarations, including `let ... else:`, assignments, returns, `if`, `while`, single-form and parallel `for`, `match`, `defer`, `unsafe`, and deferred cleanup bodies that `await`
|
|
479
479
|
- await placement is handled by the normalization pass which hoists nested awaits into `let` bindings, so `await` is supported in all expression contexts (call arguments, binary operations, if-expr, match-expr, member access, index access, format strings)
|
|
480
480
|
|
|
481
|
-
The default async model is a language-integrated entry boundary. `std.async` remains the explicit high-level helper surface for operations such as `sleep`, `work`, `completed`, `result`, `wait`, `wait_on`, and `with_runtime`, while the normal runtime model stays the single integrated libuv-backed runtime.
|
|
481
|
+
The default async model is a language-integrated entry boundary. `std.async` remains the explicit high-level helper surface for operations such as `sleep`, `work`, `completed`, `result`, `wait`, `run`, `wait_on`, and `with_runtime`, while the normal runtime model stays the single integrated libuv-backed runtime.
|
|
482
482
|
|
|
483
483
|
#### Concurrency: `parallel for` and `parallel:` blocks
|
|
484
484
|
|
data/docs/language-manual.md
CHANGED
|
@@ -1172,7 +1172,9 @@ let q = quat(x = 0.0, y = 0.0, z = 0.0, w = 1.0)
|
|
|
1172
1172
|
- for pointer-like bases (`ptr[T]`, `const_ptr[T]`, `own[T]`, `cstr`, `fn(...)`, `proc(...)`, opaque), `T?` is a nullable pointer with `null` as the absent value
|
|
1173
1173
|
- for non-pointer value bases (`int`, `bool`, `float`, structs, ...), `T?` is stored inline by value as a tagged optional (a presence flag plus the value); it copies by value with no hidden heap allocation or pointer aliasing
|
|
1174
1174
|
- `null` expresses absence in any nullable context; the explicit typed `null[...]` form's target must be pointer-like
|
|
1175
|
+
- `null` is only a value of nullable types. Comparing a non-nullable pointer, `cstr`, or opaque handle against `null` is a type error: declare the value `T?` when null is possible, or compare against `zero[ptr[T]]` (also `zero[cstr]`, `zero[own[T]]`) to test the raw C zero pointer at the raw ABI level. Nullable types must use `null`, not `zero[...]`
|
|
1175
1176
|
- in nullable pointer-like contexts, `zero[ptr[T]]` is rejected; use `null` instead
|
|
1177
|
+
- null checks narrow flow: inside the `!= null` branch (and after an `== null` branch exits), the binding is treated as non-null. A further null check on a narrowed binding is still legal; the linter reports it as `redundant-null-check`
|
|
1176
1178
|
- at an FFI boundary (`external` / `foreign function` parameters and returns), only pointer-like `T?` is allowed; a non-pointer value nullable such as `int?` is rejected — use `ptr[T]?` or pass an explicit struct
|
|
1177
1179
|
|
|
1178
1180
|
### 6.4 Generics
|
|
@@ -1523,7 +1525,7 @@ mtc lint --ignore shadow file.mt
|
|
|
1523
1525
|
|
|
1524
1526
|
### 12.2 Rules
|
|
1525
1527
|
|
|
1526
|
-
The auto-fix column corresponds to `mtc lint --fix`.
|
|
1528
|
+
The auto-fix column corresponds to `mtc lint --fix`. `unused-import` is intentionally not auto-fixable: removing an import has non-local effects (extension methods and canonical hooks) the per-file linter cannot see.
|
|
1527
1529
|
|
|
1528
1530
|
| Code | Severity | Auto-fix | Description |
|
|
1529
1531
|
|---|---|---|---|
|
|
@@ -1538,23 +1540,33 @@ The auto-fix column corresponds to `mtc lint --fix`.
|
|
|
1538
1540
|
| `loop-single-iteration` | warning | — | Loop body always exits on the first iteration |
|
|
1539
1541
|
| `missing-return` | error | — | Function with a non-void return type lacks a guaranteed return on all paths |
|
|
1540
1542
|
| `noop-compound-assignment` | hint | — | Compound assignment uses an identity value and has no effect |
|
|
1543
|
+
| `owning-release-double` | warning | — | Owning binding may be released more than once |
|
|
1544
|
+
| `owning-release-leak` | warning | — | Owning binding is never released |
|
|
1541
1545
|
| `platform-api-drift` | warning | — | Public API differs across sibling platform-specific variants of the same module |
|
|
1542
1546
|
| `prefer-let` | hint | yes | `var` binding is never mutated; use `let` instead |
|
|
1543
1547
|
| `prefer-let-else` | hint | yes | Nullable guard can be rewritten as `let ... else:` |
|
|
1548
|
+
| `prefer-inline-methods` | hint | yes | Methods on a struct can be written inline inside the struct declaration |
|
|
1549
|
+
| `prefer-is-variant` | hint | — | A match that maps one variant arm to a boolean can be `expr is Arm` |
|
|
1550
|
+
| `prefer-own-ptr` | hint | — | Pointer binding used only inside `unsafe` could be `own[T]` for auto-deref |
|
|
1551
|
+
| `prefer-or-pattern` | hint | — | Adjacent match arms with identical bodies can merge with `\|` |
|
|
1552
|
+
| `prefer-struct-with` | hint | — | Field copies from another value can use `.with(...)` |
|
|
1553
|
+
| `prefer-try` | hint | — | A match that only propagates the failure branch can use `expr?` |
|
|
1544
1554
|
| `prefer-var-else` | hint | yes | Nullable guard can be rewritten as `var ... else:` |
|
|
1545
1555
|
| `redundant-bool-compare` | hint | yes | Comparing a boolean expression to `true`/`false` is redundant |
|
|
1556
|
+
| `redundant-cast` | hint | yes | Explicit cast where implicit widening already works |
|
|
1546
1557
|
| `redundant-else` | warning | yes | `else` block is unnecessary because all prior branches return |
|
|
1547
1558
|
| `redundant-ignored-match-binding` | hint | yes | Ignored `as _` match binding is redundant |
|
|
1548
1559
|
| `redundant-null-check` | hint | — | Null check on a value already known to be non-null by flow analysis |
|
|
1549
1560
|
| `redundant-return` | hint | yes | Final bare `return` in a `void` function is unnecessary |
|
|
1561
|
+
| `redundant-type-annotation` | hint | yes | Type annotation is redundant; the type is inferred from the initializer |
|
|
1562
|
+
| `redundant-unsafe` | hint | yes | `unsafe` block contains no unsafe operations and can be removed |
|
|
1550
1563
|
| `reserved-primitive-name` | warning | yes | Binding uses a reserved built-in type name in its active namespace |
|
|
1551
1564
|
| `self-assignment` | warning | — | Variable is assigned to itself |
|
|
1552
1565
|
| `self-comparison` | warning | — | Value is compared to itself, making the condition constant |
|
|
1553
1566
|
| `shadow` | warning | — | Local binding shadows an outer binding with the same name |
|
|
1554
1567
|
| `trailing-list-comma` | hint | yes | Trailing comma in call argument list is redundant |
|
|
1555
1568
|
| `unreachable-code` | warning | — | Code after a guaranteed terminator cannot execute |
|
|
1556
|
-
| `
|
|
1557
|
-
| `unused-import` | warning | yes | Import alias is never referenced |
|
|
1569
|
+
| `unused-import` | warning | — | Import alias is never referenced |
|
|
1558
1570
|
| `unused-local` | warning | — | Local binding is never referenced |
|
|
1559
1571
|
| `unused-param` | warning | — | Parameter is never referenced |
|
|
1560
1572
|
| `useless-expression` | warning | — | Expression statement has no side effects and its result is unused |
|
data/lib/milk_tea/base.rb
CHANGED
|
@@ -141,6 +141,27 @@ module MilkTea
|
|
|
141
141
|
"GuiLoadIcons" => "ptr[ptr[char]]?",
|
|
142
142
|
).freeze
|
|
143
143
|
|
|
144
|
+
# stb_image.h documents these as returning NULL on failure (and
|
|
145
|
+
# stbi_failure_reason as returning NULL before the first failure), so
|
|
146
|
+
# the raw binding must expose them as nullable for null checks to work.
|
|
147
|
+
stb_image_function_return_overrides = {
|
|
148
|
+
"stbi_failure_reason" => "cstr?",
|
|
149
|
+
"stbi_load" => "ptr[stbi_uc]?",
|
|
150
|
+
"stbi_load_16" => "ptr[stbi_us]?",
|
|
151
|
+
"stbi_load_16_from_callbacks" => "ptr[stbi_us]?",
|
|
152
|
+
"stbi_load_16_from_memory" => "ptr[stbi_us]?",
|
|
153
|
+
"stbi_load_from_callbacks" => "ptr[stbi_uc]?",
|
|
154
|
+
"stbi_load_from_memory" => "ptr[stbi_uc]?",
|
|
155
|
+
"stbi_load_gif_from_memory" => "ptr[stbi_uc]?",
|
|
156
|
+
"stbi_loadf" => "ptr[float]?",
|
|
157
|
+
"stbi_loadf_from_callbacks" => "ptr[float]?",
|
|
158
|
+
"stbi_loadf_from_memory" => "ptr[float]?",
|
|
159
|
+
"stbi_zlib_decode_malloc" => "ptr[char]?",
|
|
160
|
+
"stbi_zlib_decode_malloc_guesssize" => "ptr[char]?",
|
|
161
|
+
"stbi_zlib_decode_malloc_guesssize_headerflag" => "ptr[char]?",
|
|
162
|
+
"stbi_zlib_decode_noheader_malloc" => "ptr[char]?",
|
|
163
|
+
}.freeze
|
|
164
|
+
|
|
144
165
|
sdl3_documented_function_param_overrides = {
|
|
145
166
|
"SDL_AcquireGPUSwapchainTexture" => {
|
|
146
167
|
"swapchain_texture_width" => "ptr[uint]?",
|
|
@@ -1269,6 +1290,7 @@ module MilkTea
|
|
|
1269
1290
|
binding_path: root.join("std/c/stb_image.mt"),
|
|
1270
1291
|
include_directives: ["stb_image.h"],
|
|
1271
1292
|
declaration_name_prefixes: ["stbi_", "STBI_"],
|
|
1293
|
+
function_return_type_overrides: stb_image_function_return_overrides,
|
|
1272
1294
|
header_candidates: [
|
|
1273
1295
|
MilkTea.data_root.join("third_party/stb-upstream/stb_image.h").to_s,
|
|
1274
1296
|
],
|
|
@@ -31,8 +31,10 @@ module MilkTea
|
|
|
31
31
|
else
|
|
32
32
|
[]
|
|
33
33
|
end
|
|
34
|
+
debug_guard_receivers = collect_debug_null_guards_for_statement(statement)
|
|
34
35
|
statement_lines = with_checked_index_aliases(aliases) do
|
|
35
|
-
|
|
36
|
+
debug_guard_lines = emit_debug_null_guard_lines(debug_guard_receivers, indent, function)
|
|
37
|
+
debug_guard_lines + case statement
|
|
36
38
|
when IR::LocalDecl
|
|
37
39
|
if array_type?(statement.type) && statement.value.is_a?(IR::Call)
|
|
38
40
|
lines = ["#{indent}#{c_declaration(statement.type, statement.linkage_name)};"]
|
|
@@ -524,6 +526,121 @@ module MilkTea
|
|
|
524
526
|
nil
|
|
525
527
|
end
|
|
526
528
|
|
|
529
|
+
# ── debug null traps ────────────────────────────────────────────────────
|
|
530
|
+
# Debug builds insert `if (ptr == NULL) mt_fatal(...)` before every
|
|
531
|
+
# unconditionally-evaluated raw pointer dereference (pointer field
|
|
532
|
+
# access, pointer indexing, read(ptr)) so a NULL that crosses the type
|
|
533
|
+
# system's non-null contract — typically from an FFI boundary — fatales
|
|
534
|
+
# with a function name instead of segfaulting. Receivers are duplicated
|
|
535
|
+
# into the guard, so only side-effect-free receivers qualify, and
|
|
536
|
+
# conditional subtrees (and/or right sides, ?: branches) are skipped so
|
|
537
|
+
# derefs that would never execute are not reported. ref[T] receivers are
|
|
538
|
+
# excluded: refs are non-null by the type system, so a trap there would
|
|
539
|
+
# be pure noise.
|
|
540
|
+
|
|
541
|
+
def collect_debug_null_guards_for_statement(statement)
|
|
542
|
+
return [] unless @debug_guards
|
|
543
|
+
|
|
544
|
+
expressions = case statement
|
|
545
|
+
when IR::LocalDecl
|
|
546
|
+
[statement.value]
|
|
547
|
+
when IR::Assignment
|
|
548
|
+
[statement.target, statement.value]
|
|
549
|
+
when IR::ExpressionStmt
|
|
550
|
+
[statement.expression]
|
|
551
|
+
when IR::ReturnStmt
|
|
552
|
+
statement.value ? [statement.value] : []
|
|
553
|
+
when IR::IfStmt
|
|
554
|
+
[statement.condition]
|
|
555
|
+
when IR::WhileStmt
|
|
556
|
+
[statement.condition]
|
|
557
|
+
when IR::ForStmt
|
|
558
|
+
[statement.init, statement.condition, statement.post].compact
|
|
559
|
+
when IR::SwitchStmt
|
|
560
|
+
[statement.expression]
|
|
561
|
+
else
|
|
562
|
+
[]
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
receivers = []
|
|
566
|
+
expressions.compact.each do |expression|
|
|
567
|
+
collect_debug_null_guard_receivers(expression, receivers)
|
|
568
|
+
end
|
|
569
|
+
receivers
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def collect_debug_null_guard_receivers(expression, receivers)
|
|
573
|
+
case expression
|
|
574
|
+
when IR::Member
|
|
575
|
+
collect_debug_null_guard_receivers(expression.receiver, receivers)
|
|
576
|
+
add_debug_null_guard_receiver(receivers, expression.receiver)
|
|
577
|
+
when IR::Index
|
|
578
|
+
collect_debug_null_guard_receivers(expression.receiver, receivers)
|
|
579
|
+
collect_debug_null_guard_receivers(expression.index, receivers)
|
|
580
|
+
add_debug_null_guard_receiver(receivers, expression.receiver)
|
|
581
|
+
when IR::Unary
|
|
582
|
+
collect_debug_null_guard_receivers(expression.operand, receivers)
|
|
583
|
+
add_debug_null_guard_receiver(receivers, expression.operand) if expression.operator == "*"
|
|
584
|
+
when IR::Call
|
|
585
|
+
collect_debug_null_guard_receivers(expression.callee, receivers) unless expression.callee.is_a?(String)
|
|
586
|
+
expression.arguments.each { |argument| collect_debug_null_guard_receivers(argument, receivers) }
|
|
587
|
+
when IR::Binary
|
|
588
|
+
collect_debug_null_guard_receivers(expression.left, receivers)
|
|
589
|
+
return if expression.operator == "and" || expression.operator == "or"
|
|
590
|
+
|
|
591
|
+
collect_debug_null_guard_receivers(expression.right, receivers)
|
|
592
|
+
when IR::Conditional
|
|
593
|
+
collect_debug_null_guard_receivers(expression.condition, receivers)
|
|
594
|
+
when IR::AddressOf, IR::Cast, IR::ReinterpretExpr
|
|
595
|
+
collect_debug_null_guard_receivers(expression.expression, receivers)
|
|
596
|
+
when IR::CheckedIndex, IR::CheckedSpanIndex, IR::NullableIndex, IR::NullableSpanIndex
|
|
597
|
+
collect_debug_null_guard_receivers(expression.receiver, receivers)
|
|
598
|
+
collect_debug_null_guard_receivers(expression.index, receivers)
|
|
599
|
+
when IR::AggregateLiteral
|
|
600
|
+
expression.fields.each { |field| collect_debug_null_guard_receivers(field.value, receivers) }
|
|
601
|
+
when IR::ArrayLiteral
|
|
602
|
+
expression.elements.each { |element| collect_debug_null_guard_receivers(element, receivers) }
|
|
603
|
+
when IR::VariantLiteral
|
|
604
|
+
expression.fields.each { |field| collect_debug_null_guard_receivers(field.value, receivers) }
|
|
605
|
+
when IR::SimdLaneWith
|
|
606
|
+
collect_debug_null_guard_receivers(expression.src, receivers)
|
|
607
|
+
collect_debug_null_guard_receivers(expression.value, receivers)
|
|
608
|
+
when IR::Assignment, IR::LocalDecl
|
|
609
|
+
collect_debug_null_guard_receivers(expression.target, receivers) if expression.is_a?(IR::Assignment)
|
|
610
|
+
collect_debug_null_guard_receivers(expression.value, receivers) if expression.value
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def add_debug_null_guard_receiver(receivers, receiver_expression)
|
|
615
|
+
return unless debug_null_guardable_receiver?(receiver_expression)
|
|
616
|
+
return if receivers.any? { |existing| existing.equal?(receiver_expression) }
|
|
617
|
+
|
|
618
|
+
receivers << receiver_expression
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def debug_null_guardable_receiver?(expression)
|
|
622
|
+
type = expression.respond_to?(:type) ? expression.type : nil
|
|
623
|
+
return false if ref_type?(type)
|
|
624
|
+
|
|
625
|
+
raw_pointer_type?(type) || nullable_pointer_like_type?(type)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def emit_debug_null_guard_lines(receivers, indent, function)
|
|
629
|
+
return [] if receivers.empty?
|
|
630
|
+
|
|
631
|
+
message = "null pointer dereferenced in #{function.linkage_name}"
|
|
632
|
+
lines = []
|
|
633
|
+
emitted = Set.new
|
|
634
|
+
receivers.each do |receiver|
|
|
635
|
+
receiver_text = emit_expression(receiver)
|
|
636
|
+
next if emitted.include?(receiver_text)
|
|
637
|
+
|
|
638
|
+
emitted << receiver_text
|
|
639
|
+
lines << "#{indent}if (#{receiver_text} == NULL) mt_fatal(\"#{message}\");"
|
|
640
|
+
end
|
|
641
|
+
lines
|
|
642
|
+
end
|
|
643
|
+
|
|
527
644
|
def statements_require_scope?(statements)
|
|
528
645
|
statements.any? { |statement| statement.is_a?(IR::LocalDecl) }
|
|
529
646
|
end
|
|
@@ -127,6 +127,13 @@ module MilkTea
|
|
|
127
127
|
|
|
128
128
|
def infer_value_type(handle_expression, env:)
|
|
129
129
|
handle_type = infer_expression_type(handle_expression, env:)
|
|
130
|
+
# BUG FIX: nullable pointer handles (ptr[T]?) read through as their
|
|
131
|
+
# base pointer type; std/net relies on this after flow-unguarded
|
|
132
|
+
# null checks on storage fields.
|
|
133
|
+
if handle_type.is_a?(Types::Nullable) &&
|
|
134
|
+
(ref_type?(handle_type.base) || pointer_type?(handle_type.base))
|
|
135
|
+
handle_type = handle_type.base
|
|
136
|
+
end
|
|
130
137
|
return referenced_type(handle_type) if ref_type?(handle_type)
|
|
131
138
|
return pointee_type(handle_type) if pointer_type?(handle_type)
|
|
132
139
|
|
|
@@ -371,7 +378,7 @@ module MilkTea
|
|
|
371
378
|
elements = value.map { |element| lower_compile_time_literal(element, element_type) }
|
|
372
379
|
return nil if elements.any?(&:nil?)
|
|
373
380
|
|
|
374
|
-
IR::ArrayLiteral.new(type:, elements:)
|
|
381
|
+
return IR::ArrayLiteral.new(type:, elements:)
|
|
375
382
|
when Hash
|
|
376
383
|
return nil unless type.is_a?(Types::Struct)
|
|
377
384
|
fields = value.map do |name, field_value|
|
|
@@ -721,8 +721,10 @@ module MilkTea
|
|
|
721
721
|
raise_sema_error("operator #{expression.operator} is not supported for type #{bad_type}")
|
|
722
722
|
end
|
|
723
723
|
end
|
|
724
|
-
unless common_numeric_type(left_type, right_type) || types_compatible?(left_type, right_type) || types_compatible?(right_type, left_type)
|
|
725
|
-
|
|
724
|
+
unless common_numeric_type(left_type, right_type) || types_compatible?(left_type, right_type) || types_compatible?(right_type, left_type) || refined_null_comparison?(expression, scopes)
|
|
725
|
+
message = "operator #{expression.operator} requires comparable types, got #{left_type} and #{right_type}"
|
|
726
|
+
hint = null_comparison_hint(left_type, right_type)
|
|
727
|
+
raise_sema_error(hint ? "#{message}; #{hint}" : message)
|
|
726
728
|
end
|
|
727
729
|
|
|
728
730
|
@ctx.types.fetch("bool")
|
|
@@ -731,6 +733,45 @@ module MilkTea
|
|
|
731
733
|
end
|
|
732
734
|
end
|
|
733
735
|
|
|
736
|
+
# A null comparison on a nullable binding stays legal after flow
|
|
737
|
+
# refinement has narrowed the binding to non-null: the declared storage
|
|
738
|
+
# is still nullable, and the check is a redundant-but-valid test (the
|
|
739
|
+
# linter reports it as redundant-null-check). Only bare `null` qualifies;
|
|
740
|
+
# a typed `null[...]` with a mismatched target stays a type error.
|
|
741
|
+
def refined_null_comparison?(expression, scopes)
|
|
742
|
+
if expression.left.is_a?(AST::NullLiteral) && expression.left.type.nil? && expression.right.is_a?(AST::Identifier)
|
|
743
|
+
identifier_expression = expression.right
|
|
744
|
+
elsif expression.right.is_a?(AST::NullLiteral) && expression.right.type.nil? && expression.left.is_a?(AST::Identifier)
|
|
745
|
+
identifier_expression = expression.left
|
|
746
|
+
else
|
|
747
|
+
return false
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
binding = lookup_value(identifier_expression.name, scopes)
|
|
751
|
+
binding&.storage_type.is_a?(Types::Nullable)
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
# A null comparison against a non-nullable operand is a contract mismatch,
|
|
755
|
+
# not just incompatible operand types: the message should say which fix
|
|
756
|
+
# applies (nullable declaration, raw zero comparison, or removal).
|
|
757
|
+
def null_comparison_hint(left_type, right_type)
|
|
758
|
+
other = if left_type.is_a?(Types::Null) && !right_type.is_a?(Types::Null)
|
|
759
|
+
right_type
|
|
760
|
+
elsif right_type.is_a?(Types::Null) && !left_type.is_a?(Types::Null)
|
|
761
|
+
left_type
|
|
762
|
+
end
|
|
763
|
+
return nil unless other
|
|
764
|
+
return nil if other.is_a?(Types::Nullable) || other.is_a?(Types::Null)
|
|
765
|
+
|
|
766
|
+
if ref_type?(other)
|
|
767
|
+
"#{other} is never null by design; refs cannot be compared against null"
|
|
768
|
+
elsif pointer_type?(other) || opaque_type?(other) || other == Types::Registry.primitive("cstr")
|
|
769
|
+
hint = "#{other} is declared non-null; declare it #{other}? if null is possible"
|
|
770
|
+
hint << ", or compare against zero[#{other}] for raw zero-pointer storage" if zero_supported_type?(other)
|
|
771
|
+
hint
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
|
|
734
775
|
def infer_if_expression(expression, scopes:, expected_type: nil)
|
|
735
776
|
condition_type = infer_expression(expression.condition, scopes:, expected_type: @ctx.types.fetch("bool"))
|
|
736
777
|
ensure_assignable!(condition_type, @ctx.types.fetch("bool"), "if expression condition must be bool, got #{condition_type}")
|
|
@@ -740,6 +740,12 @@ module MilkTea
|
|
|
740
740
|
end
|
|
741
741
|
|
|
742
742
|
def zero_initializable_type?(type, operation: "zero")
|
|
743
|
+
return true if zero_supported_type?(type)
|
|
744
|
+
|
|
745
|
+
raise_sema_error("#{operation} does not support type #{type}")
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
def zero_supported_type?(type)
|
|
743
749
|
return true if type.is_a?(Types::Primitive) && !type.void?
|
|
744
750
|
return true if type.is_a?(Types::Nullable)
|
|
745
751
|
return true if type.is_a?(Types::EnumBase)
|
|
@@ -761,7 +767,7 @@ module MilkTea
|
|
|
761
767
|
return true if simd_type?(type)
|
|
762
768
|
return true if atomic_type?(type)
|
|
763
769
|
|
|
764
|
-
|
|
770
|
+
false
|
|
765
771
|
end
|
|
766
772
|
|
|
767
773
|
def layout_aggregate_type?(type)
|
|
@@ -101,6 +101,11 @@ module MilkTea
|
|
|
101
101
|
return missing_option_value(option) unless value
|
|
102
102
|
|
|
103
103
|
options[:nullable_report_path] = value
|
|
104
|
+
when "--nullable-policy"
|
|
105
|
+
value = @argv.shift
|
|
106
|
+
return missing_option_value(option) unless value
|
|
107
|
+
|
|
108
|
+
options[:nullable_policy_path] = value
|
|
104
109
|
else
|
|
105
110
|
@err.puts("unknown bindgen option #{option}")
|
|
106
111
|
print_help
|
|
@@ -109,9 +114,60 @@ module MilkTea
|
|
|
109
114
|
end
|
|
110
115
|
|
|
111
116
|
options[:include_directives] = nil if options[:include_directives].empty?
|
|
117
|
+
if options[:nullable_policy_path]
|
|
118
|
+
policy = load_nullable_policy(options.delete(:nullable_policy_path))
|
|
119
|
+
return nil unless policy
|
|
120
|
+
|
|
121
|
+
options[:function_return_type_overrides] = policy[:return_types]
|
|
122
|
+
options[:function_param_type_overrides] = policy[:parameters]
|
|
123
|
+
end
|
|
112
124
|
options
|
|
113
125
|
end
|
|
114
126
|
|
|
127
|
+
# A nullable policy lists the symbols a header documents as returning NULL
|
|
128
|
+
# (or taking NULL-able out-pointers) so generated raw bindings expose them
|
|
129
|
+
# as nullable `T?` without hand-annotating the C header. Entries feed the
|
|
130
|
+
# same function_return_type_overrides / function_param_type_overrides
|
|
131
|
+
# machinery as the raw binding registry and appear in the nullable report.
|
|
132
|
+
def load_nullable_policy(path)
|
|
133
|
+
require "json"
|
|
134
|
+
policy_path = File.expand_path(path)
|
|
135
|
+
unless File.file?(policy_path)
|
|
136
|
+
@err.puts("nullable policy file not found: #{path}")
|
|
137
|
+
return nil
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
document = begin
|
|
141
|
+
JSON.parse(File.read(policy_path))
|
|
142
|
+
rescue JSON::ParserError => e
|
|
143
|
+
@err.puts("nullable policy is not valid JSON: #{e.message}")
|
|
144
|
+
return nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
unless document.is_a?(Hash)
|
|
148
|
+
@err.puts("nullable policy must be a JSON object")
|
|
149
|
+
return nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
unknown_keys = document.keys - ["return_types", "parameters"]
|
|
153
|
+
unless unknown_keys.empty?
|
|
154
|
+
@err.puts("nullable policy has unknown keys: #{unknown_keys.join(', ')} (expected return_types, parameters)")
|
|
155
|
+
return nil
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
return_types = document["return_types"] || {}
|
|
159
|
+
parameters = document["parameters"] || {}
|
|
160
|
+
policy_shape_valid = return_types.is_a?(Hash) && return_types.keys.all?(String) && return_types.values.all?(String) &&
|
|
161
|
+
parameters.is_a?(Hash) && parameters.keys.all?(String) &&
|
|
162
|
+
parameters.values.all? { |value| value.is_a?(Hash) && value.keys.all?(String) && value.values.all?(String) }
|
|
163
|
+
unless policy_shape_valid
|
|
164
|
+
@err.puts('nullable policy must map {"return_types": {symbol: "type?"}, "parameters": {symbol: {param: "type?"}}}')
|
|
165
|
+
return nil
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
{ return_types:, parameters: }
|
|
169
|
+
end
|
|
170
|
+
|
|
115
171
|
def missing_option_value(option)
|
|
116
172
|
@err.puts("missing value for #{option}")
|
|
117
173
|
print_help
|
data/lib/milk_tea/tooling/cli.rb
CHANGED
|
@@ -776,6 +776,7 @@ module MilkTea
|
|
|
776
776
|
|
|
777
777
|
Options:
|
|
778
778
|
-o, --output OUTPUT Write the generated module to this file.
|
|
779
|
+
--nullable-policy PATH Apply a JSON nullable policy of symbols documented as returning NULL.
|
|
779
780
|
--nullable-report PATH Write the remaining manual nullable policy report to this file.
|
|
780
781
|
--link LIB Link against this library (repeatable).
|
|
781
782
|
--include HEADER Extra #include directive (repeatable).
|
|
@@ -911,7 +912,7 @@ module MilkTea
|
|
|
911
912
|
io.puts(" mtc deps lock [PATH_OR_PACKAGE] [--check]")
|
|
912
913
|
io.puts(" mtc deps publish [PATH_OR_PACKAGE] [--upstream]")
|
|
913
914
|
io.puts(" mtc deps fetch [PATH_OR_PACKAGE]")
|
|
914
|
-
io.puts(" mtc bindgen MODULE HEADER [-o OUTPUT] [--nullable-report PATH] [--link LIB] [--include HEADER] [--clang PATH] [--clang-arg ARG]")
|
|
915
|
+
io.puts(" mtc bindgen MODULE HEADER [-o OUTPUT] [--nullable-policy PATH] [--nullable-report PATH] [--link LIB] [--include HEADER] [--clang PATH] [--clang-arg ARG]")
|
|
915
916
|
io.puts(" mtc cache purge|status")
|
|
916
917
|
io.puts(" mtc docs [--open] [--port PORT]")
|
|
917
918
|
io.puts(" mtc std list [--json]")
|
data/std/c/stb_image.mt
CHANGED
|
@@ -11,16 +11,16 @@ struct stbi_io_callbacks:
|
|
|
11
11
|
skip: fn(arg0: ptr[void], arg1: int) -> void
|
|
12
12
|
eof: fn(arg0: ptr[void]) -> int
|
|
13
13
|
|
|
14
|
-
external function stbi_load_from_memory(buffer: const_ptr[stbi_uc], len: int, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_uc]
|
|
15
|
-
external function stbi_load_from_callbacks(clbk: const_ptr[stbi_io_callbacks], user: ptr[void], x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_uc]
|
|
16
|
-
external function stbi_load(filename: cstr, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_uc]
|
|
17
|
-
external function stbi_load_gif_from_memory(buffer: const_ptr[stbi_uc], len: int, delays: ptr[ptr[int]], x: ptr[int], y: ptr[int], z: ptr[int], comp: ptr[int], req_comp: int) -> ptr[stbi_uc]
|
|
18
|
-
external function stbi_load_16_from_memory(buffer: const_ptr[stbi_uc], len: int, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_us]
|
|
19
|
-
external function stbi_load_16_from_callbacks(clbk: const_ptr[stbi_io_callbacks], user: ptr[void], x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_us]
|
|
20
|
-
external function stbi_load_16(filename: cstr, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_us]
|
|
21
|
-
external function stbi_loadf_from_memory(buffer: const_ptr[stbi_uc], len: int, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[float]
|
|
22
|
-
external function stbi_loadf_from_callbacks(clbk: const_ptr[stbi_io_callbacks], user: ptr[void], x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[float]
|
|
23
|
-
external function stbi_loadf(filename: cstr, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[float]
|
|
14
|
+
external function stbi_load_from_memory(buffer: const_ptr[stbi_uc], len: int, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_uc]?
|
|
15
|
+
external function stbi_load_from_callbacks(clbk: const_ptr[stbi_io_callbacks], user: ptr[void], x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_uc]?
|
|
16
|
+
external function stbi_load(filename: cstr, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_uc]?
|
|
17
|
+
external function stbi_load_gif_from_memory(buffer: const_ptr[stbi_uc], len: int, delays: ptr[ptr[int]], x: ptr[int], y: ptr[int], z: ptr[int], comp: ptr[int], req_comp: int) -> ptr[stbi_uc]?
|
|
18
|
+
external function stbi_load_16_from_memory(buffer: const_ptr[stbi_uc], len: int, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_us]?
|
|
19
|
+
external function stbi_load_16_from_callbacks(clbk: const_ptr[stbi_io_callbacks], user: ptr[void], x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_us]?
|
|
20
|
+
external function stbi_load_16(filename: cstr, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[stbi_us]?
|
|
21
|
+
external function stbi_loadf_from_memory(buffer: const_ptr[stbi_uc], len: int, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[float]?
|
|
22
|
+
external function stbi_loadf_from_callbacks(clbk: const_ptr[stbi_io_callbacks], user: ptr[void], x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[float]?
|
|
23
|
+
external function stbi_loadf(filename: cstr, x: ptr[int], y: ptr[int], channels_in_file: ptr[int], desired_channels: int) -> ptr[float]?
|
|
24
24
|
external function stbi_hdr_to_ldr_gamma(gamma: float) -> void
|
|
25
25
|
external function stbi_hdr_to_ldr_scale(scale: float) -> void
|
|
26
26
|
external function stbi_ldr_to_hdr_gamma(gamma: float) -> void
|
|
@@ -28,7 +28,7 @@ external function stbi_ldr_to_hdr_scale(scale: float) -> void
|
|
|
28
28
|
external function stbi_is_hdr_from_callbacks(clbk: const_ptr[stbi_io_callbacks], user: ptr[void]) -> int
|
|
29
29
|
external function stbi_is_hdr_from_memory(buffer: const_ptr[stbi_uc], len: int) -> int
|
|
30
30
|
external function stbi_is_hdr(filename: cstr) -> int
|
|
31
|
-
external function stbi_failure_reason() -> cstr
|
|
31
|
+
external function stbi_failure_reason() -> cstr?
|
|
32
32
|
external function stbi_image_free(retval_from_stbi_load: ptr[void]) -> void
|
|
33
33
|
external function stbi_info_from_memory(buffer: const_ptr[stbi_uc], len: int, x: ptr[int], y: ptr[int], comp: ptr[int]) -> int
|
|
34
34
|
external function stbi_info_from_callbacks(clbk: const_ptr[stbi_io_callbacks], user: ptr[void], x: ptr[int], y: ptr[int], comp: ptr[int]) -> int
|
|
@@ -42,11 +42,11 @@ external function stbi_set_flip_vertically_on_load(flag_true_if_should_flip: int
|
|
|
42
42
|
external function stbi_set_unpremultiply_on_load_thread(flag_true_if_should_unpremultiply: int) -> void
|
|
43
43
|
external function stbi_convert_iphone_png_to_rgb_thread(flag_true_if_should_convert: int) -> void
|
|
44
44
|
external function stbi_set_flip_vertically_on_load_thread(flag_true_if_should_flip: int) -> void
|
|
45
|
-
external function stbi_zlib_decode_malloc_guesssize(buffer: cstr, len: int, initial_size: int, outlen: ptr[int]) -> ptr[char]
|
|
46
|
-
external function stbi_zlib_decode_malloc_guesssize_headerflag(buffer: cstr, len: int, initial_size: int, outlen: ptr[int], parse_header: int) -> ptr[char]
|
|
47
|
-
external function stbi_zlib_decode_malloc(buffer: cstr, len: int, outlen: ptr[int]) -> ptr[char]
|
|
45
|
+
external function stbi_zlib_decode_malloc_guesssize(buffer: cstr, len: int, initial_size: int, outlen: ptr[int]) -> ptr[char]?
|
|
46
|
+
external function stbi_zlib_decode_malloc_guesssize_headerflag(buffer: cstr, len: int, initial_size: int, outlen: ptr[int], parse_header: int) -> ptr[char]?
|
|
47
|
+
external function stbi_zlib_decode_malloc(buffer: cstr, len: int, outlen: ptr[int]) -> ptr[char]?
|
|
48
48
|
external function stbi_zlib_decode_buffer(obuffer: ptr[char], olen: int, ibuffer: cstr, ilen: int) -> int
|
|
49
|
-
external function stbi_zlib_decode_noheader_malloc(buffer: cstr, len: int, outlen: ptr[int]) -> ptr[char]
|
|
49
|
+
external function stbi_zlib_decode_noheader_malloc(buffer: cstr, len: int, outlen: ptr[int]) -> ptr[char]?
|
|
50
50
|
external function stbi_zlib_decode_noheader_buffer(obuffer: ptr[char], olen: int, ibuffer: cstr, ilen: int) -> int
|
|
51
51
|
|
|
52
52
|
const STBI_VERSION: int = 1
|
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.4.
|
|
4
|
+
version: 0.4.26
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Long (Teefan) Tran
|
|
@@ -632,7 +632,7 @@ metadata:
|
|
|
632
632
|
homepage_uri: https://teefan.github.io/mt-lang/
|
|
633
633
|
source_code_uri: https://github.com/teefan/mt-lang
|
|
634
634
|
post_install_message: |
|
|
635
|
-
Milk Tea 0.4.
|
|
635
|
+
Milk Tea 0.4.26 installed!
|
|
636
636
|
|
|
637
637
|
System requirements:
|
|
638
638
|
- A C compiler (gcc or clang) must be available on PATH
|